Skip to content

Commit e2bfd5c

Browse files
justin808claude
andcommitted
docs: Improve file-system-based bundle generation guide
Add comprehensive improvements to the file-system-based automated bundle generation documentation to address gaps that cause confusion for both human developers and AI coding agents. ## Key improvements: ### 1. Layout Integration Clarification - Added detailed section explaining empty pack tag placeholders in Rails layouts - Clarified how react_component helper automatically calls append_javascript_pack_tag - Explained the connection between component usage and automatic bundle injection - Showed the complete flow from component rendering to CSS/JS loading ### 2. Complete Working Example - Added step-by-step setup guide from scratch - Included real component implementations with CSS modules and dynamic imports - Provided complete Rails controller, view, and layout code examples - Demonstrated proper directory structure with concrete file paths - Showed bundle splitting benefits with actual size comparisons (50KB vs 2.7MB) ### 3. Comprehensive Troubleshooting Section - Documented all common issues encountered during implementation - Added solutions for FOUC handling across different development modes - Covered SSR compatibility issues with dynamic imports - Included installation order requirements (Shakapacker before React on Rails) - Provided debug techniques for troubleshooting bundle loading - Addressed bundle size optimization and development vs production differences ### 4. Enhanced Developer Experience - Clear explanations of "why" behind configuration choices - Explicit file naming conventions and patterns - Debug mode instructions for investigating bundle loading issues - Common failure modes and their solutions These improvements make the documentation significantly more practical and reduce the likelihood of implementation errors. The additions are based on real-world implementation experience and address the most common stumbling blocks. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 0e826ac commit e2bfd5c

File tree

1 file changed

+360
-0
lines changed

1 file changed

+360
-0
lines changed

docs/guides/file-system-based-automated-bundle-generation.md

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,259 @@ For example, if you wanted to utilize our file-system based entrypoint generatio
219219
220220
The default value of the `auto_load_bundle` parameter can be specified by setting `config.auto_load_bundle` in `config/initializers/react_on_rails.rb` and thus removed from each call to `react_component`.
221221
222+
### Layout Integration with Auto-Loading
223+
224+
When using `auto_load_bundle: true`, your Rails layout needs to include empty pack tag placeholders where React on Rails will inject the component-specific CSS and JavaScript bundles automatically:
225+
226+
```erb
227+
<!DOCTYPE html>
228+
<html>
229+
<head>
230+
<!-- Your regular head content -->
231+
<%= csrf_meta_tags %>
232+
<%= csp_meta_tag %>
233+
234+
<!-- Empty pack tags - React on Rails injects component CSS/JS here -->
235+
<%= stylesheet_pack_tag %>
236+
<%= javascript_pack_tag %>
237+
</head>
238+
<body>
239+
<%= yield %>
240+
</body>
241+
</html>
242+
```
243+
244+
**How it works:**
245+
246+
1. **Component calls automatically append bundles**: When you use `<%= react_component("ComponentName", props, auto_load_bundle: true) %>` in a view, React on Rails automatically calls `append_javascript_pack_tag "generated/ComponentName"` and `append_stylesheet_pack_tag "generated/ComponentName"` (in static/production modes).
247+
248+
2. **Layout renders appended bundles**: The empty `<%= stylesheet_pack_tag %>` and `<%= javascript_pack_tag %>` calls in your layout are where the appended component bundles get rendered.
249+
250+
3. **No manual bundle management**: You don't need to manually specify which bundles to load - React on Rails handles this automatically based on which components are used in each view.
251+
252+
**Example with multiple components:**
253+
254+
If your view contains:
255+
```erb
256+
<%= react_component("HelloWorld", @hello_world_props, auto_load_bundle: true) %>
257+
<%= react_component("HeavyMarkdownEditor", @editor_props, auto_load_bundle: true) %>
258+
```
259+
260+
React on Rails automatically generates HTML equivalent to:
261+
```erb
262+
<!-- In <head> where <%= stylesheet_pack_tag %> appears -->
263+
<%= stylesheet_pack_tag "generated/HelloWorld" %>
264+
<%= stylesheet_pack_tag "generated/HeavyMarkdownEditor" %>
265+
266+
<!-- Before </body> where <%= javascript_pack_tag %> appears -->
267+
<%= javascript_pack_tag "generated/HelloWorld" %>
268+
<%= javascript_pack_tag "generated/HeavyMarkdownEditor" %>
269+
```
270+
271+
This enables optimal bundle splitting where each page only loads the CSS and JavaScript needed for the components actually used on that page.
272+
273+
## Complete Working Example
274+
275+
Here's a step-by-step example showing how to set up file-system-based automated bundle generation from scratch:
276+
277+
### 1. Configure Shakapacker
278+
279+
In `config/shakapacker.yml`:
280+
281+
```yml
282+
default: &default
283+
source_path: app/javascript
284+
source_entry_path: packs
285+
public_root_path: public
286+
public_output_path: packs
287+
nested_entries: true # Required for auto-generation
288+
cache_manifest: false
289+
```
290+
291+
### 2. Configure React on Rails
292+
293+
In `config/initializers/react_on_rails.rb`:
294+
295+
```rb
296+
ReactOnRails.configure do |config|
297+
config.components_subdirectory = "ror_components" # Directory name for auto-registered components
298+
config.auto_load_bundle = true # Enable automatic bundle loading
299+
config.server_bundle_js_file = "server-bundle.js"
300+
end
301+
```
302+
303+
### 3. Directory Structure
304+
305+
Set up your directory structure like this:
306+
307+
```text
308+
app/javascript/
309+
└── src/
310+
├── HelloWorld/
311+
│ ├── HelloWorld.module.css # Component styles
312+
│ └── ror_components/ # Auto-registration directory
313+
│ └── HelloWorld.jsx # React component
314+
└── HeavyMarkdownEditor/
315+
├── HeavyMarkdownEditor.module.css # Component styles
316+
└── ror_components/ # Auto-registration directory
317+
└── HeavyMarkdownEditor.jsx # React component
318+
```
319+
320+
### 4. Component Implementation
321+
322+
`app/javascript/src/HelloWorld/ror_components/HelloWorld.jsx`:
323+
324+
```jsx
325+
import React from 'react';
326+
import styles from '../HelloWorld.module.css';
327+
328+
const HelloWorld = ({ name }) => (
329+
<div className={styles.hello}>
330+
<h1>Hello {name}!</h1>
331+
<p>Welcome to React on Rails with auto-registration!</p>
332+
</div>
333+
);
334+
335+
export default HelloWorld;
336+
```
337+
338+
`app/javascript/src/HeavyMarkdownEditor/ror_components/HeavyMarkdownEditor.jsx`:
339+
340+
```jsx
341+
import React, { useState, useEffect } from 'react';
342+
import styles from '../HeavyMarkdownEditor.module.css';
343+
344+
const HeavyMarkdownEditor = ({ initialContent = '# Hello\n\nStart editing!' }) => {
345+
const [content, setContent] = useState(initialContent);
346+
const [ReactMarkdown, setReactMarkdown] = useState(null);
347+
const [remarkGfm, setRemarkGfm] = useState(null);
348+
349+
// Dynamic imports for SSR compatibility
350+
useEffect(() => {
351+
const loadMarkdown = async () => {
352+
const [{ default: ReactMarkdown }, { default: remarkGfm }] = await Promise.all([
353+
import('react-markdown'),
354+
import('remark-gfm')
355+
]);
356+
setReactMarkdown(() => ReactMarkdown);
357+
setRemarkGfm(() => remarkGfm);
358+
};
359+
loadMarkdown();
360+
}, []);
361+
362+
if (!ReactMarkdown) {
363+
return <div className={styles.loading}>Loading editor...</div>;
364+
}
365+
366+
return (
367+
<div className={styles.editor}>
368+
<div className={styles.input}>
369+
<h3>Markdown Input:</h3>
370+
<textarea
371+
value={content}
372+
onChange={(e) => setContent(e.target.value)}
373+
className={styles.textarea}
374+
/>
375+
</div>
376+
<div className={styles.output}>
377+
<h3>Preview:</h3>
378+
<div className={styles.preview}>
379+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
380+
</div>
381+
</div>
382+
</div>
383+
);
384+
};
385+
386+
export default HeavyMarkdownEditor;
387+
```
388+
389+
### 5. Rails Layout
390+
391+
`app/views/layouts/application.html.erb`:
392+
393+
```erb
394+
<!DOCTYPE html>
395+
<html>
396+
<head>
397+
<title>React on Rails Auto-Registration Demo</title>
398+
<meta name="viewport" content="width=device-width,initial-scale=1">
399+
<%= csrf_meta_tags %>
400+
<%= csp_meta_tag %>
401+
402+
<!-- Empty pack tags - React on Rails injects component CSS/JS here -->
403+
<%= stylesheet_pack_tag %>
404+
<%= javascript_pack_tag %>
405+
</head>
406+
407+
<body>
408+
<%= yield %>
409+
</body>
410+
</html>
411+
```
412+
413+
### 6. Rails Views and Controller
414+
415+
`app/controllers/hello_world_controller.rb`:
416+
417+
```rb
418+
class HelloWorldController < ApplicationController
419+
def index
420+
@hello_world_props = { name: 'Auto-Registration' }
421+
end
422+
423+
def editor
424+
@editor_props = {
425+
initialContent: "# Welcome to the Heavy Editor\n\nThis component demonstrates:\n- Dynamic imports for SSR\n- Bundle splitting\n- Automatic CSS loading"
426+
}
427+
end
428+
end
429+
```
430+
431+
`app/views/hello_world/index.html.erb`:
432+
433+
```erb
434+
<%= react_component("HelloWorld", @hello_world_props, prerender: true) %>
435+
```
436+
437+
`app/views/hello_world/editor.html.erb`:
438+
439+
```erb
440+
<%= react_component("HeavyMarkdownEditor", @editor_props, prerender: true) %>
441+
```
442+
443+
### 7. Generate Bundles
444+
445+
Run the pack generation command:
446+
447+
```bash
448+
bundle exec rake react_on_rails:generate_packs
449+
```
450+
451+
This creates:
452+
- `app/javascript/packs/generated/HelloWorld.js`
453+
- `app/javascript/packs/generated/HeavyMarkdownEditor.js`
454+
455+
### 8. Update .gitignore
456+
457+
```gitignore
458+
# Generated React on Rails packs
459+
**/generated/**
460+
```
461+
462+
### 9. Start the Server
463+
464+
Now when you visit your pages, React on Rails automatically:
465+
- Loads only the CSS and JS needed for components on each page
466+
- Registers components without manual `ReactOnRails.register()` calls
467+
- Enables optimal bundle splitting and caching
468+
469+
**Bundle sizes in this example:**
470+
- HelloWorld: ~50KB (lightweight)
471+
- HeavyMarkdownEditor: ~2.7MB (heavy with 58+ dependencies)
472+
473+
Each page loads only what it needs!
474+
222475
### Server Rendering and Client Rendering Components
223476

224477
If server rendering is enabled, the component will be registered for usage both in server and client rendering. To have separate definitions for client and server rendering, name the component files `ComponentName.server.jsx` and `ComponentName.client.jsx`. The `ComponentName.server.jsx` file will be used for server rendering and the `ComponentName.client.jsx` file for client rendering. If you don't want the component rendered on the server, you should only have the `ComponentName.client.jsx` file.
@@ -233,3 +486,110 @@ Once generated, all server entrypoints will be imported into a file named `[Reac
233486
### Using Automated Bundle Generation Feature with already defined packs
234487

235488
As of version 13.3.4, bundles inside directories that match `config.components_subdirectory` will be automatically added as entrypoints, while bundles outside those directories need to be manually added to the `Shakapacker.config.source_entry_path` or Webpack's `entry` rules.
489+
490+
## Troubleshooting
491+
492+
### Common Issues and Solutions
493+
494+
#### 1. "Component not found" errors
495+
496+
**Problem**: `react_component` helper throws "Component not found" error.
497+
498+
**Solutions**:
499+
- Ensure your component is in a `ror_components` directory (or your configured `components_subdirectory`)
500+
- Run `rake react_on_rails:generate_packs` to generate the component bundles
501+
- Check that your component exports a default export: `export default MyComponent;`
502+
- Verify the component name matches the directory structure
503+
504+
#### 2. CSS not loading (FOUC - Flash of Unstyled Content)
505+
506+
**Problem**: Components load but CSS styles are missing or delayed.
507+
508+
**Solutions**:
509+
- **Development with HMR**: FOUC is expected. Use `bin/dev static` mode for development without FOUC
510+
- **Production**: Ensure CSS extraction is working by checking for `.css` files in `public/packs/css/generated/`
511+
- **Layout**: Verify your layout includes empty `<%= stylesheet_pack_tag %>` placeholder
512+
- Check that CSS files are properly imported in your components: `import styles from './Component.module.css';`
513+
514+
#### 3. "document is not defined" errors during SSR
515+
516+
**Problem**: Server-side rendering fails with browser-only API access.
517+
518+
**Solutions**:
519+
- Use dynamic imports for browser-only libraries:
520+
```jsx
521+
useEffect(() => {
522+
const loadLibrary = async () => {
523+
const { default: BrowserLibrary } = await import('browser-library');
524+
setLibrary(() => BrowserLibrary);
525+
};
526+
loadLibrary();
527+
}, []);
528+
```
529+
- Provide fallback/skeleton components during loading
530+
- Consider client-only rendering: use `ComponentName.client.jsx` files only
531+
532+
#### 4. Bundles not being generated
533+
534+
**Problem**: Running `rake react_on_rails:generate_packs` doesn't create files.
535+
536+
**Solutions**:
537+
- Verify `nested_entries: true` in `shakapacker.yml`
538+
- Check that `components_subdirectory` is correctly configured
539+
- Ensure components are in the right directory structure: `src/ComponentName/ror_components/ComponentName.jsx`
540+
- Make sure you're using the correct source path in Shakapacker config
541+
542+
#### 5. Manual pack tags not working after switching to auto-loading
543+
544+
**Problem**: Manually specified `javascript_pack_tag` or `stylesheet_pack_tag` break.
545+
546+
**Solutions**:
547+
- Remove specific pack names from manual pack tags: use `<%= javascript_pack_tag %>` instead of `<%= javascript_pack_tag 'specific-bundle' %>`
548+
- Remove manual `append_javascript_pack_tag` calls - `react_component` with `auto_load_bundle: true` handles this automatically
549+
- Delete any client bundle entry files (e.g., `client-bundle.js`) that manually register components
550+
551+
#### 6. Bundle size issues
552+
553+
**Problem**: Large bundles loading when not needed.
554+
555+
**Solutions**:
556+
- Use component-level bundle splitting - each page loads only needed components
557+
- Implement dynamic imports for heavy dependencies
558+
- Check bundle analysis with `RAILS_ENV=production NODE_ENV=production bundle exec rails assets:precompile` and examine generated bundle sizes
559+
- Consider code splitting within heavy components
560+
561+
#### 7. Development vs Production differences
562+
563+
**Problem**: Works in development but fails in production.
564+
565+
**Solutions**:
566+
- **CSS**: Production extracts CSS to separate files, development might inline it
567+
- **Source maps**: Check if source maps are causing issues in production
568+
- **Minification**: Some code might break during minification - check console for errors
569+
- **Environment**: Use `bin/dev prod` to test production-like assets locally
570+
571+
#### 8. Installation order issues
572+
573+
**Problem**: React on Rails installation fails or behaves unexpectedly.
574+
575+
**Solutions**:
576+
- **Correct order**: Always install Shakapacker BEFORE React on Rails
577+
```bash
578+
bundle add shakapacker
579+
rails shakapacker:install
580+
bundle add react_on_rails
581+
rails generate react_on_rails:install
582+
```
583+
- If you installed in wrong order, uninstall and reinstall in correct sequence
584+
585+
### Debug Mode
586+
587+
To debug auto-loading behavior, temporarily add logging to see what bundles are being loaded:
588+
589+
```erb
590+
<!-- Temporarily add this to your layout to see what gets loaded -->
591+
<%= debug(content_for(:javascript_pack_tags)) %>
592+
<%= debug(content_for(:stylesheet_pack_tags)) %>
593+
```
594+
595+
This helps verify that components are correctly appending their bundles.

0 commit comments

Comments
 (0)