
Security News
Nx npm Packages Compromised in Supply Chain Attack Weaponizing AI CLI Tools
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socketβs AI scanner detected the supply chain attack and flagged the malware.
Launch quickly: upgrade with vite only if necessary (MYAGNI).
IslandJS Rails supports the development of React islands in Rails apps by synchronizing package.json
dependencies with UMD libraries served in public/islands/vendor
.
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.
IslandJS Rails requires:
# Add to your Gemfile
gem 'islandjs-rails'
bundle install
rails islandjs:init
rails "islandjs:install[react,18.3.1]"
rails "islandjs:install[react-dom,18.3.1]"
yarn watch
<!-- In any view -->
<%= react_component('DashboardApp', { userId: current_user.id }) %>
<!-- With placeholder (v0.2.0+) to prevent layout shift -->
<%= react_component('DashboardApp', { userId: current_user.id }) do %>
<div class="loading-skeleton">Loading dashboard...</div>
<% end %>
yarn build # you may remove any stale islandjs bundles before committing
π‘ Turbo Cache Compatible: React components automatically persist state across Turbo navigation! See Turbo Cache Integration for details.
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.
The props data passed into react_component
is automatically available via useTurboProps
and can be optionally cached using useTurboCache
for persistence across Turbo navigation.
// jsx/components/DashboardApp.jsx
import React, { useState, useEffect } from 'react';
import { useTurboProps, useTurboCache } from '../utils/turbo.js';
function DashboardApp({ containerId }) {
// Read initial state from data-initial-state attribute
const initialProps = useTurboProps(containerId);
const [userId] = useState(initialProps.userId);
const [welcomeCount, setWelcomeCount] = useState(initialProps.welcomeCount || 0);
// Setup turbo cache persistence for state across navigation
useEffect(() => {
const cleanup = useTurboCache(containerId, { userId, welcomeCount }, true);
return cleanup;
}, [containerId, userId, welcomeCount]);
return (
<div>
<h2>Welcome user {userId}!</h2>
<p>You've visited this dashboard {welcomeCount} times</p>
<button onClick={() => setWelcomeCount(prev => prev + 1)}>
Visit Again
</button>
</div>
);
}
export default DashboardApp;
IslandJS Rails aligns perfectly with Rails 8's philosophy of simplicity and convention over configuration:
Modern Rails developers face a painful choice:
IslandJS Rails offers a middle way: a simple, zero-config solution for adding React and other JS libraries to your Rails app. Get 80% of reactive use cases covered for 5% of the hassle. Most apps don't even need the other 20% anyway.
# Instead of complex vite/webpack configuration:
rails "islandjs:install[react,18.3.1]"
rails "islandjs:install[react-beautiful-dnd]"
rails "islandjs:install[quill]"
rails "islandjs:install[recharts]"
rails "islandjs:install[lodash]"
Result: Zero-to-no webpack configuration, instant prod builds, access to hundreds of UMD packages.
\\ in SomeComponent.jsx
const quill = new window.Quill("#editor", {
theme: "snow",
});
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+).
If you absolutely need a package that doesn't ship UMD builds, you have a few options:
islandjs-rails
installvite
with turbo-mount & write your component that way. You can migrate incrementally from islandj-rails
to turbo-mount
if preferred β they can coexist in the same app.β
Tested against Rails 8
β
Compatible with Rails 8 asset pipeline
β
Optimized for Hotwire/Turbo workflows
β
Zero-config React islands
# Initialize IslandJS Rails in your project
rails islandjs:init
# Install packages (adds to package.json + saves to vendor directory)
rails "islandjs:install[react]"
rails "islandjs:install[react,18.3.1]" # With specific version
rails "islandjs:install[lodash]"
# Update packages (updates package.json + refreshes vendor files)
rails "islandjs:update[react]"
rails "islandjs:update[react,18.3.1]" # To specific version
# Remove packages (removes from package.json + deletes vendor files)
rails "islandjs:remove[react]"
rails "islandjs:remove[lodash]"
# Clean all UMD files (removes ALL vendor files)
rails islandjs:clean
# Show configuration
rails islandjs:config
IslandJS Rails includes additional tasks for managing the vendor file system:
# Rebuild the combined vendor bundle (when using :external_combined mode)
rails islandjs:vendor:rebuild
# Show vendor system status and file sizes
rails islandjs:vendor:status
Vendor System Modes:
:external_split
(default): Each library served as separate file from public/islands/vendor/
:external_combined
: All libraries concatenated into single bundle with cache-busting hashBenefits of Vendor System:
For development and building your JavaScript:
# Development - watch for changes and rebuild automatically
yarn watch
# Or with npm: npm run watch
# Production - build optimized bundle for deployment
yarn build
# Or with npm: npm run build
# Install dependencies (after adding packages via islandjs:install)
yarn install
# Or with npm: npm install
Development Workflow:
yarn watch
(or npm run watch
) in one terminalapp/javascript/islands/components/
public/
Production Deployment:
yarn build
(or npm run build
) to create optimized bundlegit add public/islands_* && git add public/islands/*
Scoped packages are npm packages that belong to a namespace, prefixed with @
. Examples include:
@solana/web3.js
When installing scoped packages, you must include the full package name with the @
symbol:
# β
Correct - Full scoped package name
rails "islandjs:install[@solana/web3.js,1.98.4]"
# β Incorrect - Missing .js suffix
rails "islandjs:install[@solana/web3,1.98.4]"
# β Incorrect - Missing scope
rails "islandjs:install[web3.js,1.98.4]"
The @
symbol is handled automatically by Rails task syntax when using double quotes. No additional escaping is needed:
# β
Works perfectly
rails "islandjs:install[@solana/web3.js]"
# β
Also works (with version)
rails "islandjs:install[@solana/web3.js,1.98.4]"
# β οΈ May not work in some shells without quotes
rails islandjs:install[@solana/web3.js] # Avoid this
IslandJS Rails automatically converts scoped package names to valid JavaScript global names:
# Automatic conversions:
'@solana/web3.js' => 'solanaWeb3' # Scope removed, camelCase
You can override the automatic global name detection for scoped packages:
Solana Web3.js is automatically detected with the built-in global name mapping solanaWeb3
.
Once installed, scoped packages work exactly like regular packages:
// jsx/components/SolanaComponent.jsx
import React from 'react';
function SolanaComponent() {
// solanaWeb3 is automatically available as a global variable on the window object
const connection = new window.solanaWeb3.Connection('https://api.devnet.solana.com');
return (
<div>
<h2>Solana Integration</h2>
<p>Connected to: {connection.rpcEndpoint}</p>
</div>
);
}
export default SolanaComponent;
IslandJS Rails automatically configures webpack externals for scoped packages:
// webpack.config.js (auto-generated)
module.exports = {
externals: {
// IslandJS Rails managed externals - do not edit manually
"@solana/web3.js": "solanaWeb3",
"react": "React",
"react-dom": "ReactDOM"
},
// ... rest of config
};
Issue: Package not found
# Check the exact package name on npm
npm view @solana/web3.js
# Ensure you're using the full name
rails "islandjs:install[@solana/web3.js]" # β
Correct
rails "islandjs:install[@solana/web3]" # β Wrong
Issue: UMD not available
# Some scoped packages don't ship UMD builds
# Check package documentation or try alternatives
# Future IslandJS Rails versions will support local UMD generation
Command | What it does | Example |
---|---|---|
install | Adds package via yarn + downloads UMD + saves to vendor | rails islandjs:install[react] |
update | Updates package version + refreshes UMD | rails islandjs:update[react,18.3.1] |
remove | Removes package via yarn + deletes vendor files | rails islandjs:remove[react] |
clean | Removes ALL vendor files (destructive!) | rails islandjs:clean |
# config/initializers/islandjs.rb
IslandjsRails.configure do |config|
# Directory for ERB partials (default: app/views/shared/islands)
config.partials_dir = Rails.root.join('app/views/shared/islands')
# Webpack configuration path
config.webpack_config_path = Rails.root.join('webpack.config.js')
# Vendor file delivery mode (default: :external_split)
config.vendor_script_mode = :external_split # One file per library
# config.vendor_script_mode = :external_combined # Single combined bundle
# Vendor files directory (default: public/islands/vendor)
config.vendor_dir = Rails.root.join('public/islands/vendor')
# Combined bundle filename base (default: 'islands-vendor')
config.combined_basename = 'islands-vendor'
# Library loading order for combined bundles
config.vendor_order = ['react', 'react-dom', 'lodash']
end
islands
Single helper that includes all UMD vendor scripts and your webpack bundle.
<%= islands %>
This automatically loads:
react_component(name, props, options, &block)
Renders a React component with Turbo-compatible lifecycle and optional placeholder support.
<%= react_component('UserProfile', {
userId: current_user.id,
theme: 'dark'
}, {
container_id: 'profile-widget',
namespace: 'window.islandjsRails'
}) %>
Available Options:
container_id
: Custom ID for the container elementnamespace
: JavaScript namespace for component access (default: window.islandjsRails
)tag
: HTML tag for container (default: div
)class
: CSS class for containerplaceholder_class
: CSS class for placeholder contentplaceholder_style
: Inline styles for placeholder contentβ‘ New in v0.2.0 - Prevent layout shift when React components mount!
The react_component
helper now supports placeholder content that displays while your React component loads, eliminating the "jumpy" effect common in dynamic content updates via Turbo Streams.
When React components mount (especially via Turbo Stream updates), there's often a brief moment where content height changes, causing layout shift:
<!-- Before: Content jumps when component mounts -->
<%= react_component("Reactions", { postId: post.id }) %>
<!-- Page content shifts down when reactions component renders -->
<%= react_component("Reactions", { postId: post.id }) do %>
<div class="reactions-skeleton">
<div class="skeleton-button">π</div>
<div class="skeleton-button">β€οΈ</div>
<div class="skeleton-button">π</div>
<div class="skeleton-count">Loading...</div>
</div>
<% end %>
<%= react_component("Reactions", { postId: post.id }, {
placeholder_class: "reactions-skeleton"
}) %>
<%= react_component("Reactions", { postId: post.id }, {
placeholder_style: "height: 40px; background: #f8f9fa; border-radius: 4px;"
}) %>
Placeholders shine in Turbo Stream scenarios where content updates dynamically:
<!-- app/views/posts/_reactions.html.erb -->
<%= turbo_stream.replace "post_#{@post.id}_reactions" do %>
<%= react_component("Reactions", {
postId: @post.id,
initialCount: @post.reactions.count
}) do %>
<div class="reactions-placeholder" style="height: 32px;">
<span class="text-muted">Loading reactions...</span>
</div>
<% end %>
<% end %>
IslandJS Rails includes built-in Turbo cache compatibility for React components, ensuring state persists seamlessly across navigation.
The react_component
helper automatically:
data-initial-state
attributesThis allows React components to persist state changes back to the data attribute before turbo caches the page.
See the complete working example: HelloWorld.jsx
import React, { useState, useEffect } from 'react';
import { useTurboProps, useTurboCache } from '../utils/turbo.js';
const HelloWorld = ({ containerId }) => {
// Read initial state from data-initial-state attribute
const initialProps = useTurboProps(containerId);
const [count, setCount] = useState(initialProps.count || 0);
const [message, setMessage] = useState(initialProps.message || "Hello!");
// ensures persists state across Turbo navigation
useEffect(() => {
const cleanup = useTurboCache(containerId, { count, message }, true);
return cleanup;
}, [containerId, count, message]);
return (
<div>
<p>{message}</p>
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
</div>
);
};
<!-- In any Rails view -->
<%= react_component('HelloWorld', {
message: 'Hello from Rails!',
count: 5
}) %>
IslandJS Rails provides utility functions for Turbo compatibility:
// Get initial state from container's data attribute
const initialProps = useTurboProps(containerId);
// Set up automatic state persistence
const cleanup = useTurboCache(containerId, currentState, autoRestore);
// Manually persist state (if needed)
persistState(containerId, stateObject);
react_component
helperIslandJS Rails includes built-in global name mappings for popular libraries:
react
β React
react-dom
β ReactDOM
lodash
β _
@solana/web3.js
β solanaWeb3
For other packages, kebab-case names are automatically converted to camelCase.
// Create your own namespace (or use the default window.islandjsRails)
window.islandjsRails = {
React: window.React,
UI: window.MaterialUI,
Utils: window._,
Charts: window.Chart
};
// Use in components
const { React, UI, Utils } = window.islandjsRails;
IslandJS Rails automatically updates your webpack externals:
// webpack.config.js (auto-generated)
module.exports = {
externals: {
'react': 'React',
'lodash': '_'
}
};
IslandjsRails.configure do |config|
# Directory for ERB partials (default: app/views/shared/islands)
config.partials_dir = Rails.root.join('app/views/shared/islands')
# Path to webpack config (default: webpack.config.js)
config.webpack_config_path = Rails.root.join('webpack.config.js')
# Path to package.json (default: package.json)
config.package_json_path = Rails.root.join('package.json')
# Vendor file delivery mode (default: :external_split)
config.vendor_script_mode = :external_split # One file per library
# config.vendor_script_mode = :external_combined # Single combined bundle
# Vendor files directory (default: public/islands/vendor)
config.vendor_dir = Rails.root.join('public/islands/vendor')
# Combined bundle filename base (default: 'islands-vendor')
config.combined_basename = 'islands-vendor'
# Library loading order for combined bundles
config.vendor_order = ['react', 'react-dom', 'lodash']
# Built-in global name mappings are automatically applied
# No custom configuration needed for common libraries
end
Package not found on CDN:
# Some packages don't publish UMD builds
# Check unpkg.com/package-name/ for available files
# Consider using a different package or requesting UMD support
Global name conflicts: 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.
Webpack externals not updating:
# Sync to update externals
rails islandjs:sync
# Or clean and reinstall
rails islandjs:clean
rails islandjs:install[react]
git checkout -b feature/amazing-feature
)bundle exec rspec
)git commit -am 'Add amazing feature'
)git push origin feature/amazing-feature
)MIT License - see LICENSE file for details.
cd lib/islandjs_rails
bundle install
bundle exec rspec
# View coverage in terminal
bundle exec rspec
# Open coverage report in browser
open coverage/index.html
Planned features for future releases:
FAQs
Unknown package
We found that islandjs-rails demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socketβs AI scanner detected the supply chain attack and flagged the malware.
Security News
CISAβs 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.
Security News
A clarification on our recent research investigating 60 malicious Ruby gems.