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/README.md
ADDED
@@ -0,0 +1,754 @@
|
|
1
|
+
# IslandJS Rails
|
2
|
+
|
3
|
+
[](https://github.com/praxis-emergent/islandjs-rails/actions/workflows/github-actions-demo.yml)
|
4
|
+
[](coverage/index.html)
|
5
|
+
[](spec/)
|
6
|
+
[](#rails-8-ready)
|
7
|
+
[](https://www.ruby-lang.org/)
|
8
|
+
|
9
|
+
IslandJS Rails supports the development of React (or other JS library) islands in Rails apps by synchronizing `package.json` defined dependencies with UMD libraries served in `public/islands/vendor`.
|
10
|
+
|
11
|
+
Write Turbo compatible JSX in `app/javascript/islands/components/` and render it with a `react_component` helper in ERB templates (including Turbo Stream partials) — Vue and other framework support can be added with a bit of work.
|
12
|
+
|
13
|
+
## Quick Start
|
14
|
+
|
15
|
+
IslandJS Rails requires:
|
16
|
+
- **Node.js** 16+ with **npm** and **yarn** (development only)
|
17
|
+
- **Rails** 7+ (tested with Rails 8)
|
18
|
+
|
19
|
+
### Installation
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# Add to your Gemfile
|
23
|
+
gem 'islandjs-rails'
|
24
|
+
```
|
25
|
+
|
26
|
+
```bash
|
27
|
+
bundle install
|
28
|
+
rails islandjs:init
|
29
|
+
```
|
30
|
+
|
31
|
+
### Install React
|
32
|
+
```bash
|
33
|
+
rails "islandjs:install[react,18.3.1]"
|
34
|
+
rails "islandjs:install[react-dom,18.3.1]"
|
35
|
+
```
|
36
|
+
|
37
|
+
### Render React Components
|
38
|
+
```erb
|
39
|
+
<!-- In any view -->
|
40
|
+
<%= react_component('DashboardApp', { userId: current_user.id }) %>
|
41
|
+
```
|
42
|
+
|
43
|
+
> 💡 **Turbo Cache Compatible**: React components automatically persist state across Turbo navigation! See [Turbo Cache Integration](#turbo-cache-integration) for details.
|
44
|
+
|
45
|
+
### Write Modern JSX (with Turbo Cache Support)
|
46
|
+
|
47
|
+
Every React component should be written to accept a single `containerId` prop and rendered using the `react_component` view helper, which accepts a JSON object of props.
|
48
|
+
|
49
|
+
The props data passed into `react_component` is automatically available via `useTurboProps` and can be optionally cached using `useTurboCache` for persistence across Turbo navigation.
|
50
|
+
|
51
|
+
```jsx
|
52
|
+
// jsx/components/DashboardApp.jsx
|
53
|
+
import React, { useState, useEffect } from 'react';
|
54
|
+
import { useTurboProps, useTurboCache } from '../utils/turbo.js';
|
55
|
+
|
56
|
+
function DashboardApp({ containerId }) {
|
57
|
+
// Read initial state from data-initial-state attribute
|
58
|
+
const initialProps = useTurboProps(containerId);
|
59
|
+
|
60
|
+
const [userId] = useState(initialProps.userId);
|
61
|
+
const [welcomeCount, setWelcomeCount] = useState(initialProps.welcomeCount || 0);
|
62
|
+
|
63
|
+
// Setup turbo cache persistence for state across navigation
|
64
|
+
useEffect(() => {
|
65
|
+
const cleanup = useTurboCache(containerId, { userId, welcomeCount }, true);
|
66
|
+
return cleanup;
|
67
|
+
}, [containerId, userId, welcomeCount]);
|
68
|
+
|
69
|
+
return (
|
70
|
+
<div>
|
71
|
+
<h2>Welcome user {userId}!</h2>
|
72
|
+
<p>You've visited this dashboard {welcomeCount} times</p>
|
73
|
+
<button onClick={() => setWelcomeCount(prev => prev + 1)}>
|
74
|
+
Visit Again
|
75
|
+
</button>
|
76
|
+
</div>
|
77
|
+
);
|
78
|
+
}
|
79
|
+
|
80
|
+
export default DashboardApp;
|
81
|
+
```
|
82
|
+
|
83
|
+
## Why IslandJS Rails?
|
84
|
+
|
85
|
+
### Perfect for Rails 8
|
86
|
+
IslandJS Rails aligns perfectly with **Rails 8's philosophy** of simplicity and convention over configuration:
|
87
|
+
|
88
|
+
- **Asset Pipeline Simplification**: Rails 8 streamlined assets - IslandJS Rails fits seamlessly
|
89
|
+
- **Hotwire + React Islands**: The sweet spot for Rails 8 frontend development
|
90
|
+
- **Fast Development & Deployment**: Instant builds, no library rebundling
|
91
|
+
|
92
|
+
### The Problem IslandJS Rails Solves
|
93
|
+
Modern Rails developers face a painful choice:
|
94
|
+
- **Bundle everything**: Massive webpack configs, slow builds, bundle bloat
|
95
|
+
- **Skip modern JS**: Miss out on React and popular npm packages
|
96
|
+
|
97
|
+
**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)).
|
98
|
+
|
99
|
+
### The IslandJS Rails Solution
|
100
|
+
```bash
|
101
|
+
# Instead of complex webpack configuration:
|
102
|
+
rails "islandjs:install[react]"
|
103
|
+
rails "islandjs:install[lodash]"
|
104
|
+
```
|
105
|
+
|
106
|
+
**Result**: Zero-to-no webpack configuration, instant prod builds, access to hundreds of UMD packages.
|
107
|
+
|
108
|
+
## Rails 8 Ready
|
109
|
+
|
110
|
+
✅ **Tested against Rails 8**
|
111
|
+
✅ **Compatible with Rails 8 asset pipeline**
|
112
|
+
✅ **Optimized for Hotwire/Turbo workflows**
|
113
|
+
✅ **Zero-config React islands**
|
114
|
+
|
115
|
+
## Core Features
|
116
|
+
|
117
|
+
- **Convention over Configuration** - Works with sensible defaults
|
118
|
+
- **Package.json Integration** - (npm + yarn)
|
119
|
+
- **CDN Downloads** - Fetches UMD builds from unpkg.com and jsdelivr.net
|
120
|
+
- **Rails Integration** - Serves auto-generated vendor UMD files for seamless integration
|
121
|
+
- **Webpack Externals** - Updates webpack config to prevent duplicate bundling while allowing development in jsx or other formats
|
122
|
+
- **Flexible Architecture** - Compose and namespace libraries as needed
|
123
|
+
|
124
|
+
## CLI Commands
|
125
|
+
|
126
|
+
### 📦 Package Management
|
127
|
+
|
128
|
+
#### Rails Tasks
|
129
|
+
|
130
|
+
```bash
|
131
|
+
# Initialize IslandJS Rails in your project
|
132
|
+
rails islandjs:init
|
133
|
+
|
134
|
+
# Install packages (adds to package.json + saves to vendor directory)
|
135
|
+
rails "islandjs:install[react]"
|
136
|
+
rails "islandjs:install[react,18.3.1]" # With specific version
|
137
|
+
rails "islandjs:install[lodash]"
|
138
|
+
|
139
|
+
# Update packages (updates package.json + refreshes vendor files)
|
140
|
+
rails "islandjs:update[react]"
|
141
|
+
rails "islandjs:update[react,18.3.1]" # To specific version
|
142
|
+
|
143
|
+
# Remove packages (removes from package.json + deletes vendor files)
|
144
|
+
rails "islandjs:remove[react]"
|
145
|
+
rails "islandjs:remove[lodash]"
|
146
|
+
|
147
|
+
# Clean all UMD files (removes ALL vendor files)
|
148
|
+
rails islandjs:clean
|
149
|
+
|
150
|
+
# Show configuration
|
151
|
+
rails islandjs:config
|
152
|
+
```
|
153
|
+
|
154
|
+
### 🗂️ Vendor System Management
|
155
|
+
|
156
|
+
IslandJS Rails includes additional tasks for managing the vendor file system:
|
157
|
+
|
158
|
+
```bash
|
159
|
+
# Rebuild the combined vendor bundle (when using :external_combined mode)
|
160
|
+
rails islandjs:vendor:rebuild
|
161
|
+
|
162
|
+
# Show vendor system status and file sizes
|
163
|
+
rails islandjs:vendor:status
|
164
|
+
```
|
165
|
+
|
166
|
+
**Vendor System Modes:**
|
167
|
+
- **`:external_split`** (default): Each library served as separate file from `public/islands/vendor/`
|
168
|
+
- **`:external_combined`**: All libraries concatenated into single bundle with cache-busting hash
|
169
|
+
|
170
|
+
**Benefits of Vendor System:**
|
171
|
+
- 🚀 **Better Performance**: Browser caching, parallel downloads, no Base64 bloat
|
172
|
+
- 📦 **Scalable**: File size doesn't affect HTML parsing or memory usage
|
173
|
+
- 🔧 **Maintainable**: Clear separation between vendor libraries and application code
|
174
|
+
- 🌐 **CDN Ready**: Vendor files can be easily moved to CDN for global distribution (serving from CDN will be configurable granularly in future versions — where possible)
|
175
|
+
|
176
|
+
### 🛠️ Development & Production Commands
|
177
|
+
|
178
|
+
For development and building your JavaScript:
|
179
|
+
|
180
|
+
```bash
|
181
|
+
# Development - watch for changes and rebuild automatically
|
182
|
+
yarn watch
|
183
|
+
# Or with npm: npm run watch
|
184
|
+
|
185
|
+
# Production - build optimized bundle for deployment
|
186
|
+
yarn build
|
187
|
+
# Or with npm: npm run build
|
188
|
+
|
189
|
+
# Install dependencies (after adding packages via islandjs:install)
|
190
|
+
yarn install
|
191
|
+
# Or with npm: npm install
|
192
|
+
```
|
193
|
+
|
194
|
+
**Development Workflow:**
|
195
|
+
1. Run `yarn watch` (or `npm run watch`) in one terminal
|
196
|
+
2. Edit your components in `app/javascript/islands/components/`
|
197
|
+
3. Changes are automatically compiled to `public/islands_bundle.js`
|
198
|
+
|
199
|
+
**Production Deployment:**
|
200
|
+
1. Run `yarn build` (or `npm run build`) to create optimized bundle
|
201
|
+
2. Commit the built assets: `git add public/islands_* && git add public/islands/*`
|
202
|
+
3. Deploy with confidence - assets are prebuilt
|
203
|
+
|
204
|
+
## 📦 Working with Scoped Packages
|
205
|
+
|
206
|
+
### What are Scoped Packages?
|
207
|
+
|
208
|
+
Scoped packages are npm packages that belong to a namespace, prefixed with `@`. Examples include:
|
209
|
+
- `@solana/web3.js`
|
210
|
+
|
211
|
+
### Installation Syntax
|
212
|
+
|
213
|
+
When installing scoped packages, you **must** include the full package name with the `@` symbol:
|
214
|
+
|
215
|
+
```bash
|
216
|
+
# ✅ Correct - Full scoped package name
|
217
|
+
rails "islandjs:install[@solana/web3.js,1.98.2]"
|
218
|
+
|
219
|
+
# ❌ Incorrect - Missing .js suffix
|
220
|
+
rails "islandjs:install[@solana/web3,1.98.2]"
|
221
|
+
|
222
|
+
# ❌ Incorrect - Missing scope
|
223
|
+
rails "islandjs:install[web3.js,1.98.2]"
|
224
|
+
```
|
225
|
+
|
226
|
+
### Shell Escaping
|
227
|
+
|
228
|
+
The `@` symbol is handled automatically by Rails task syntax when using double quotes. No additional escaping is needed:
|
229
|
+
|
230
|
+
```bash
|
231
|
+
# ✅ Works perfectly
|
232
|
+
rails "islandjs:install[@solana/web3.js]"
|
233
|
+
|
234
|
+
# ✅ Also works (with version)
|
235
|
+
rails "islandjs:install[@solana/web3.js,1.98.2]"
|
236
|
+
|
237
|
+
# ⚠️ May not work in some shells without quotes
|
238
|
+
rails islandjs:install[@solana/web3.js] # Avoid this
|
239
|
+
```
|
240
|
+
|
241
|
+
### Global Name Detection
|
242
|
+
|
243
|
+
IslandJS Rails automatically converts scoped package names to valid JavaScript global names:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
# Automatic conversions:
|
247
|
+
'@solana/web3.js' => 'solanaWeb3' # Scope removed, camelCase
|
248
|
+
```
|
249
|
+
|
250
|
+
### Custom Global Names
|
251
|
+
|
252
|
+
You can override the automatic global name detection for scoped packages:
|
253
|
+
|
254
|
+
Solana Web3.js is automatically detected with the built-in global name mapping `solanaWeb3`.
|
255
|
+
|
256
|
+
### Usage in Components
|
257
|
+
|
258
|
+
Once installed, scoped packages work exactly like regular packages:
|
259
|
+
|
260
|
+
```jsx
|
261
|
+
// jsx/components/SolanaComponent.jsx
|
262
|
+
import React from 'react';
|
263
|
+
|
264
|
+
function SolanaComponent() {
|
265
|
+
// solanaWeb3 is automatically available as a global variable on the window object
|
266
|
+
const connection = new window.solanaWeb3.Connection('https://api.devnet.solana.com');
|
267
|
+
|
268
|
+
return (
|
269
|
+
<div>
|
270
|
+
<h2>Solana Integration</h2>
|
271
|
+
<p>Connected to: {connection.rpcEndpoint}</p>
|
272
|
+
</div>
|
273
|
+
);
|
274
|
+
}
|
275
|
+
|
276
|
+
export default SolanaComponent;
|
277
|
+
```
|
278
|
+
|
279
|
+
### Webpack Externals
|
280
|
+
|
281
|
+
IslandJS Rails automatically configures webpack externals for scoped packages:
|
282
|
+
|
283
|
+
```javascript
|
284
|
+
// webpack.config.js (auto-generated)
|
285
|
+
module.exports = {
|
286
|
+
externals: {
|
287
|
+
// IslandJS Rails managed externals - do not edit manually
|
288
|
+
"@solana/web3.js": "solanaWeb3",
|
289
|
+
"react": "React",
|
290
|
+
"react-dom": "ReactDOM"
|
291
|
+
},
|
292
|
+
// ... rest of config
|
293
|
+
};
|
294
|
+
```
|
295
|
+
|
296
|
+
### Troubleshooting Scoped Packages
|
297
|
+
|
298
|
+
**Issue: Package not found**
|
299
|
+
```bash
|
300
|
+
# Check the exact package name on npm
|
301
|
+
npm view @solana/web3.js
|
302
|
+
|
303
|
+
# Ensure you're using the full name
|
304
|
+
rails "islandjs:install[@solana/web3.js]" # ✅ Correct
|
305
|
+
rails "islandjs:install[@solana/web3]" # ❌ Wrong
|
306
|
+
```
|
307
|
+
|
308
|
+
**Issue: UMD not available**
|
309
|
+
```bash
|
310
|
+
# Some scoped packages don't ship UMD builds
|
311
|
+
# Check package documentation or try alternatives
|
312
|
+
# Future IslandJS Rails versions will support local UMD generation
|
313
|
+
```
|
314
|
+
|
315
|
+
### ⚡ Quick Reference
|
316
|
+
|
317
|
+
| Command | What it does | Example |
|
318
|
+
|---------|--------------|---------|
|
319
|
+
| `install` | Adds package via yarn + downloads UMD + saves to vendor | `rails islandjs:install[react]` |
|
320
|
+
| `update` | Updates package version + refreshes UMD | `rails islandjs:update[react,18.3.1]` |
|
321
|
+
| `remove` | Removes package via yarn + deletes vendor files | `rails islandjs:remove[react]` |
|
322
|
+
| `clean` | Removes ALL vendor files (destructive!) | `rails islandjs:clean` |
|
323
|
+
|
324
|
+
### Configuration
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
# config/initializers/islandjs.rb
|
328
|
+
IslandjsRails.configure do |config|
|
329
|
+
# Directory for ERB partials (default: app/views/shared/islands)
|
330
|
+
config.partials_dir = Rails.root.join('app/views/shared/islands')
|
331
|
+
|
332
|
+
# Webpack configuration path
|
333
|
+
config.webpack_config_path = Rails.root.join('webpack.config.js')
|
334
|
+
|
335
|
+
# Vendor file delivery mode (default: :external_split)
|
336
|
+
config.vendor_script_mode = :external_split # One file per library
|
337
|
+
# config.vendor_script_mode = :external_combined # Single combined bundle
|
338
|
+
|
339
|
+
# Vendor files directory (default: public/islands/vendor)
|
340
|
+
config.vendor_dir = Rails.root.join('public/islands/vendor')
|
341
|
+
|
342
|
+
# Combined bundle filename base (default: 'islands-vendor')
|
343
|
+
config.combined_basename = 'islands-vendor'
|
344
|
+
|
345
|
+
# Library loading order for combined bundles
|
346
|
+
config.vendor_order = ['react', 'react-dom', 'lodash']
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
## Rails Integration
|
351
|
+
|
352
|
+
### Helpers
|
353
|
+
|
354
|
+
#### `islands`
|
355
|
+
Single helper that includes all UMD vendor scripts and your webpack bundle.
|
356
|
+
|
357
|
+
```erb
|
358
|
+
<%= islands %>
|
359
|
+
```
|
360
|
+
|
361
|
+
This automatically loads:
|
362
|
+
- All UMD libraries from vendor files (either split or combined mode)
|
363
|
+
- Your webpack bundle
|
364
|
+
- Debug information in development
|
365
|
+
|
366
|
+
#### `react_component(name, props, options)`
|
367
|
+
Renders a React component with Turbo-compatible lifecycle.
|
368
|
+
|
369
|
+
```erb
|
370
|
+
<%= react_component('UserProfile', {
|
371
|
+
userId: current_user.id,
|
372
|
+
theme: 'dark'
|
373
|
+
}, {
|
374
|
+
container_id: 'profile-widget',
|
375
|
+
namespace: 'window.islandjsRails'
|
376
|
+
}) %>
|
377
|
+
```
|
378
|
+
|
379
|
+
## Turbo Cache Integration
|
380
|
+
|
381
|
+
IslandJS Rails includes **built-in Turbo cache compatibility** for React components, ensuring state persists seamlessly across navigation.
|
382
|
+
|
383
|
+
### How It Works
|
384
|
+
|
385
|
+
The `react_component` helper automatically:
|
386
|
+
1. **Stores initial props** as JSON in `data-initial-state` attributes
|
387
|
+
2. **Generates unique container IDs** for each component instance
|
388
|
+
3. **Passes only the container ID** to the React component
|
389
|
+
|
390
|
+
This allows React components to persist state changes back to the data attribute before turbo caches the page.
|
391
|
+
|
392
|
+
### Example: Turbo-Compatible Component
|
393
|
+
|
394
|
+
See the complete working example: [`HelloWorld.jsx`](app/javascript/islands/components/HelloWorld.jsx)
|
395
|
+
|
396
|
+
```jsx
|
397
|
+
import React, { useState, useEffect } from 'react';
|
398
|
+
import { useTurboProps, useTurboCache } from '../utils/turbo.js';
|
399
|
+
|
400
|
+
const HelloWorld = ({ containerId }) => {
|
401
|
+
// Read initial state from data-initial-state attribute
|
402
|
+
const initialProps = useTurboProps(containerId);
|
403
|
+
|
404
|
+
const [count, setCount] = useState(initialProps.count || 0);
|
405
|
+
const [message, setMessage] = useState(initialProps.message || "Hello!");
|
406
|
+
|
407
|
+
// ensures persists state across Turbo navigation
|
408
|
+
useEffect(() => {
|
409
|
+
const cleanup = useTurboCache(containerId, { count, message }, true);
|
410
|
+
return cleanup;
|
411
|
+
}, [containerId, count, message]);
|
412
|
+
|
413
|
+
return (
|
414
|
+
<div>
|
415
|
+
<p>{message}</p>
|
416
|
+
<button onClick={() => setCount(count + 1)}>
|
417
|
+
Clicked {count} times
|
418
|
+
</button>
|
419
|
+
</div>
|
420
|
+
);
|
421
|
+
};
|
422
|
+
```
|
423
|
+
|
424
|
+
### Usage in Views
|
425
|
+
|
426
|
+
```erb
|
427
|
+
<!-- In any Rails view -->
|
428
|
+
<%= react_component('HelloWorld', {
|
429
|
+
message: 'Hello from Rails!',
|
430
|
+
count: 5
|
431
|
+
}) %>
|
432
|
+
```
|
433
|
+
|
434
|
+
### Live Demo
|
435
|
+
|
436
|
+
See the complete demo: [`react.html.erb`](app/views/islandjs_demo/react.html.erb)
|
437
|
+
|
438
|
+
The demo shows:
|
439
|
+
- ✅ **State persistence** across Turbo navigation
|
440
|
+
- ✅ **Automatic state restoration** when navigating back
|
441
|
+
- ✅ **Zero configuration** - works out of the box
|
442
|
+
- ✅ **Compatible with Turbo Drive** and all Hotwire features
|
443
|
+
|
444
|
+
### Turbo Utility Functions
|
445
|
+
|
446
|
+
IslandJS Rails provides utility functions for Turbo compatibility:
|
447
|
+
|
448
|
+
```javascript
|
449
|
+
// Get initial state from container's data attribute
|
450
|
+
const initialProps = useTurboProps(containerId);
|
451
|
+
|
452
|
+
// Set up automatic state persistence
|
453
|
+
const cleanup = useTurboCache(containerId, currentState, autoRestore);
|
454
|
+
|
455
|
+
// Manually persist state (if needed)
|
456
|
+
persistState(containerId, stateObject);
|
457
|
+
```
|
458
|
+
|
459
|
+
### Benefits
|
460
|
+
|
461
|
+
- **🔄 Seamless Navigation**: State survives Turbo page transitions
|
462
|
+
- **⚡ Zero Setup**: Works automatically with `react_component` helper
|
463
|
+
- **🎯 Rails-Native**: Designed specifically for Rails + Turbo workflows
|
464
|
+
- **🏝️ Island Architecture**: Each component manages its own state independently
|
465
|
+
|
466
|
+
## Advanced Usage
|
467
|
+
|
468
|
+
### Built-in Global Names
|
469
|
+
|
470
|
+
IslandJS Rails includes built-in global name mappings for popular libraries:
|
471
|
+
|
472
|
+
- `react` → `React`
|
473
|
+
- `react-dom` → `ReactDOM`
|
474
|
+
- `lodash` → `_`
|
475
|
+
- `@solana/web3.js` → `solanaWeb3`
|
476
|
+
- And more common libraries
|
477
|
+
|
478
|
+
For other packages, kebab-case names are automatically converted to camelCase.
|
479
|
+
|
480
|
+
### Composable Architecture
|
481
|
+
|
482
|
+
```javascript
|
483
|
+
// Create your own namespace (or use the default window.islandjsRails)
|
484
|
+
window.islandjsRails = {
|
485
|
+
React: window.React,
|
486
|
+
UI: window.MaterialUI,
|
487
|
+
Utils: window._,
|
488
|
+
Charts: window.Chart
|
489
|
+
};
|
490
|
+
|
491
|
+
// Use in components
|
492
|
+
const { React, UI, Utils } = window.islandjsRails;
|
493
|
+
```
|
494
|
+
|
495
|
+
### Webpack Integration
|
496
|
+
|
497
|
+
IslandJS Rails automatically updates your webpack externals:
|
498
|
+
|
499
|
+
```javascript
|
500
|
+
// webpack.config.js (auto-generated)
|
501
|
+
module.exports = {
|
502
|
+
externals: {
|
503
|
+
'react': 'React',
|
504
|
+
'lodash': '_'
|
505
|
+
}
|
506
|
+
};
|
507
|
+
```
|
508
|
+
|
509
|
+
### Configuration Options
|
510
|
+
|
511
|
+
```ruby
|
512
|
+
IslandjsRails.configure do |config|
|
513
|
+
# Directory for ERB partials (default: app/views/shared/islands)
|
514
|
+
config.partials_dir = Rails.root.join('app/views/shared/islands')
|
515
|
+
|
516
|
+
# Path to webpack config (default: webpack.config.js)
|
517
|
+
config.webpack_config_path = Rails.root.join('webpack.config.js')
|
518
|
+
|
519
|
+
# Path to package.json (default: package.json)
|
520
|
+
config.package_json_path = Rails.root.join('package.json')
|
521
|
+
|
522
|
+
# Vendor file delivery mode (default: :external_split)
|
523
|
+
config.vendor_script_mode = :external_split # One file per library
|
524
|
+
# config.vendor_script_mode = :external_combined # Single combined bundle
|
525
|
+
|
526
|
+
# Vendor files directory (default: public/islands/vendor)
|
527
|
+
config.vendor_dir = Rails.root.join('public/islands/vendor')
|
528
|
+
|
529
|
+
# Combined bundle filename base (default: 'islands-vendor')
|
530
|
+
config.combined_basename = 'islands-vendor'
|
531
|
+
|
532
|
+
# Library loading order for combined bundles
|
533
|
+
config.vendor_order = ['react', 'react-dom', 'lodash']
|
534
|
+
|
535
|
+
# Built-in global name mappings are automatically applied
|
536
|
+
# No custom configuration needed for common libraries
|
537
|
+
end
|
538
|
+
```
|
539
|
+
|
540
|
+
## Real-World Examples
|
541
|
+
|
542
|
+
### React Dashboard Component
|
543
|
+
|
544
|
+
```bash
|
545
|
+
# Install dependencies
|
546
|
+
rails islandjs:install[react]
|
547
|
+
rails islandjs:install[react-dom]
|
548
|
+
rails islandjs:install[chart.js]
|
549
|
+
```
|
550
|
+
|
551
|
+
```jsx
|
552
|
+
// jsx/components/Dashboard.jsx
|
553
|
+
import React, { useState, useEffect } from 'react';
|
554
|
+
import { useTurboProps, useTurboCache } from '../utils/turbo.js';
|
555
|
+
|
556
|
+
function Dashboard({ containerId }) {
|
557
|
+
const initialProps = useTurboProps(containerId);
|
558
|
+
|
559
|
+
const [data, setData] = useState(initialProps.data || []);
|
560
|
+
const [loading, setLoading] = useState(false);
|
561
|
+
|
562
|
+
// Setup turbo cache persistence
|
563
|
+
useEffect(() => {
|
564
|
+
const cleanup = useTurboCache(containerId, { data }, true);
|
565
|
+
return cleanup;
|
566
|
+
}, [containerId, data]);
|
567
|
+
|
568
|
+
useEffect(() => {
|
569
|
+
// Fetch dashboard data if not cached
|
570
|
+
if (data.length === 0) {
|
571
|
+
setLoading(true);
|
572
|
+
fetch('/api/dashboard')
|
573
|
+
.then(res => res.json())
|
574
|
+
.then(fetchedData => {
|
575
|
+
setData(fetchedData);
|
576
|
+
setLoading(false);
|
577
|
+
});
|
578
|
+
}
|
579
|
+
}, []);
|
580
|
+
|
581
|
+
if (loading) return <div>Loading dashboard...</div>;
|
582
|
+
|
583
|
+
return (
|
584
|
+
<div>
|
585
|
+
<h1>Dashboard</h1>
|
586
|
+
{/* Chart component here */}
|
587
|
+
<p>Data points: {data.length}</p>
|
588
|
+
</div>
|
589
|
+
);
|
590
|
+
}
|
591
|
+
|
592
|
+
export default Dashboard;
|
593
|
+
```
|
594
|
+
|
595
|
+
```erb
|
596
|
+
<!-- app/views/dashboard/show.html.erb -->
|
597
|
+
<%= islands %>
|
598
|
+
<%= react_component('Dashboard', { data: @dashboard_data }) %>
|
599
|
+
```
|
600
|
+
|
601
|
+
### Turbo + React Integration
|
602
|
+
|
603
|
+
**⭐ Recommended: Use Built-in Turbo Cache**
|
604
|
+
|
605
|
+
IslandJS Rails now includes automatic Turbo cache compatibility! See the [Turbo Cache Integration](#turbo-cache-integration) section above for the modern approach with zero manual setup.
|
606
|
+
|
607
|
+
**Alternative: Manual Turbo Integration**
|
608
|
+
|
609
|
+
For custom scenarios, you can manually handle Turbo events:
|
610
|
+
|
611
|
+
```javascript
|
612
|
+
// Manual approach (not needed with react_component helper)
|
613
|
+
document.addEventListener('turbo:load', () => {
|
614
|
+
const container = document.getElementById('react-component');
|
615
|
+
if (container && !container.hasChildNodes()) {
|
616
|
+
ReactDOM.render(
|
617
|
+
React.createElement(MyComponent, {
|
618
|
+
data: JSON.parse(container.dataset.props)
|
619
|
+
}),
|
620
|
+
container
|
621
|
+
);
|
622
|
+
}
|
623
|
+
});
|
624
|
+
|
625
|
+
document.addEventListener('turbo:before-cache', () => {
|
626
|
+
// Cleanup React components before Turbo caches
|
627
|
+
document.querySelectorAll('[data-react-component]').forEach(el => {
|
628
|
+
ReactDOM.unmountComponentAtNode(el);
|
629
|
+
});
|
630
|
+
});
|
631
|
+
```
|
632
|
+
|
633
|
+
## Troubleshooting
|
634
|
+
|
635
|
+
### Common Issues
|
636
|
+
|
637
|
+
**Package not found on CDN:**
|
638
|
+
```ruby
|
639
|
+
# Some packages don't publish UMD builds
|
640
|
+
# Check unpkg.com/package-name/ for available files
|
641
|
+
# Consider using a different package or requesting UMD support
|
642
|
+
```
|
643
|
+
|
644
|
+
**Global name conflicts:**
|
645
|
+
IslandJS Rails includes built-in mappings for common libraries. For packages with unusual global names, check the library's documentation or browser console to find the correct global variable name.
|
646
|
+
|
647
|
+
**Webpack externals not updating:**
|
648
|
+
```bash
|
649
|
+
# Sync to update externals
|
650
|
+
rails islandjs:sync
|
651
|
+
|
652
|
+
# Or clean and reinstall
|
653
|
+
rails islandjs:clean
|
654
|
+
rails islandjs:install[react]
|
655
|
+
```
|
656
|
+
|
657
|
+
## Contributing
|
658
|
+
|
659
|
+
1. Fork the repository
|
660
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
661
|
+
3. Run the tests (`bundle exec rspec`)
|
662
|
+
4. Commit your changes (`git commit -am 'Add amazing feature'`)
|
663
|
+
5. Push to the branch (`git push origin feature/amazing-feature`)
|
664
|
+
6. Open a Pull Request
|
665
|
+
|
666
|
+
## License
|
667
|
+
|
668
|
+
MIT License - see LICENSE file for details.
|
669
|
+
|
670
|
+
## Structure
|
671
|
+
|
672
|
+
```
|
673
|
+
lib/islandjs_rails/
|
674
|
+
├── spec/
|
675
|
+
│ ├── spec_helper.rb # Test setup and mocking
|
676
|
+
│ ├── lib/
|
677
|
+
│ │ ├── islandjs_rails_spec.rb # Main module tests
|
678
|
+
│ │ └── islandjs_rails/
|
679
|
+
│ │ ├── core_spec.rb # Core functionality tests
|
680
|
+
│ │ ├── rails_helpers_spec.rb # Rails helpers tests
|
681
|
+
│ │ ├── configuration_spec.rb # Configuration tests
|
682
|
+
│ │ ├── cli_spec.rb # CLI tests
|
683
|
+
│ │ ├── tasks_spec.rb # Rake tasks tests
|
684
|
+
│ │ ├── railtie_spec.rb # Rails integration tests
|
685
|
+
│ │ └── rails8_integration_spec.rb # Rails 8 specific tests
|
686
|
+
│ ├── fixtures/ # Test fixtures
|
687
|
+
│ └── support/ # Test support files
|
688
|
+
├── coverage/ # SimpleCov coverage reports
|
689
|
+
├── Gemfile # Test dependencies
|
690
|
+
├── Rakefile # Test runner configuration
|
691
|
+
└── README.md # This file
|
692
|
+
```
|
693
|
+
|
694
|
+
## Running Tests
|
695
|
+
|
696
|
+
### From the gem directory:
|
697
|
+
|
698
|
+
```bash
|
699
|
+
cd lib/islandjs_rails
|
700
|
+
bundle install
|
701
|
+
bundle exec rspec
|
702
|
+
```
|
703
|
+
|
704
|
+
### Coverage Reports:
|
705
|
+
|
706
|
+
```bash
|
707
|
+
# View coverage in terminal
|
708
|
+
bundle exec rspec
|
709
|
+
|
710
|
+
# Open coverage report in browser
|
711
|
+
open coverage/index.html
|
712
|
+
```
|
713
|
+
|
714
|
+
## Adding New Tests
|
715
|
+
|
716
|
+
When adding new IslandJS Rails functionality:
|
717
|
+
|
718
|
+
1. Add tests to the appropriate test file
|
719
|
+
2. Use the provided test helpers for consistency
|
720
|
+
3. Mock external dependencies (CDN calls, file system operations)
|
721
|
+
4. Test both success and failure scenarios
|
722
|
+
5. Ensure tests are isolated and don't affect each other
|
723
|
+
|
724
|
+
## Future Enhancements
|
725
|
+
|
726
|
+
Planned features for future releases:
|
727
|
+
|
728
|
+
- **Server-Side Rendering (SSR)**: Pre-render React components on the server
|
729
|
+
- **Component Caching**: Intelligent caching of rendered components
|
730
|
+
- **Hot Reloading**: Development mode hot reloading for React components
|
731
|
+
- **TypeScript Support**: First-class TypeScript support for UMD packages
|
732
|
+
- **Local UMD Generation**: Generate UMD builds for packages that don't ship them
|
733
|
+
- **Multi-framework Support**: Vue, Svelte, and other frameworks
|
734
|
+
- **Build-time Optimization**: Optional build-time bundling for production
|
735
|
+
- **Edge Computing**: Cloudflare Workers and similar platform support
|
736
|
+
|
737
|
+
---
|
738
|
+
|
739
|
+
## Rails 8 Integration Benefits
|
740
|
+
|
741
|
+
### 🚀 **Perfect for Rails 8 Philosophy**
|
742
|
+
- **Convention over Configuration**: Install React in one command
|
743
|
+
- **The Rails Way**: Simple, opinionated, productive
|
744
|
+
- **Modern Without Complexity**: React islands, not SPAs
|
745
|
+
|
746
|
+
### ⚡ **Performance Optimized**
|
747
|
+
- **Instant Builds**: No bundling external libraries
|
748
|
+
- **Small Bundles**: Only your app code gets bundled
|
749
|
+
- **Fast Deploys**: CDN libraries cache globally
|
750
|
+
|
751
|
+
### 🎯 **Developer Experience**
|
752
|
+
- **Zero Webpack Expertise**: Rails developers stay in Rails
|
753
|
+
- **Turbo Compatible**: Seamless navigation and caching
|
754
|
+
- **Progressive Enhancement**: Start with Hotwire, add React islands
|