Turbo Mount

repository·main·Indexed 19 days ago

https://github.com/skryukov/turbo-mount

A library for bridging Hotwire (Turbo/Stimulus) with modern JavaScript frameworks such as React, Vue, and Svelte. It enables developers to embed interactive framework components directly into Rails views while maintaining the Hotwire lifecycle, providing a Rails view helper and a TurboMountController for managing component props and mounting targets.

Tokens
6.8K
Snippets
24
Records
31
Agent score
66%

What's inside turbo-mount

  1. Auto-load Components with registerComponents

    main

    Turbo Mount provides registerComponents to automate the loading of components and controllers. This is useful for large projects where manual registration is tedious.

    Vite Integration

    Requires stimulus-vite-helpers. Use import.meta.glob to gather components and controllers.

    import plugin, { TurboMount } from "turbo-mount/react";
    import { registerComponents } from "turbo-mount/registerComponents/vite";
    
    const controllers = import.meta.glob("./**/*_controller.js", { eager: true });
    const components = import.meta.glob("/components/**/*.jsx", { eager: true });
    
    const turboMount = new TurboMount();
    registerComponents({ plugin, turboMount, components, controllers });

    ESBuild Integration

    Requires esbuild-rails. Use standard imports to gather files.

    import plugin, { TurboMount } from "turbo-mount/react";
    import { registerComponents } from "turbo-mount/registerComponents/esbuild";
    
    const turboMount = new TurboMount();
    
    import controllers from "./controllers/**/*_controller.js";
    import components from "./components/**/*.jsx";
    
    registerComponents({ plugin, turboMount, components, controllers });
  2. Install Turbo Mount

    main

    To install Turbo Mount in a Rails application, add the gem to your Gemfile and run bundle install.

    Automatic Installation

    You can automate the setup of package dependencies and initialization files by running the Rails generator:

    bin/rails generate turbo_mount:install

    Manual Installation

    If you are using a bundler like Vite, install the turbo-mount package along with your chosen framework:

    npm install turbo-mount
    # or
    yarn add turbo-mount
    
    # and the desired framework
    npm install react react-dom
    # or
    npm install vue
    # or
    npm install svelte

    If using Vite, ensure you have added the appropriate framework-specific plugins to your vite.config.js.

    gem "turbo-mount"
  3. Create Custom Controllers for Turbo Mount

    main

    To customize component behavior or pass functions (like event handlers) as props, extend TurboMountController. This allows you to use setComponentProps to update props without triggering a full Stimulus re-render of the controller itself.

    After defining the controller, pass it as the fourth argument to registerComponent.

    import { TurboMountController } from "turbo-mount";
    
    export default class extends TurboMountController {
      get componentProps() {
        return {
          ...this.propsValue,
          onChange: this.onChange.bind(this),
        };
      }
    
      onChange = (color) => {
        // Updates props without re-rendering the controller
        this.setComponentProps({ ...this.propsValue, color });
      };
    }
    
    // Registration
    import HexColorPickerController from "controllers/turbo_mount/hex_color_picker_controller";
    registerComponent(turboMount, "HexColorPicker", HexColorPicker, HexColorPickerController);
  4. Initialize Turbo Mount and Register Components

    main

    To use Turbo Mount, you must initialize the library and register your components. This is typically done in a dedicated JavaScript file (e.g., app/javascript/turbo-mount.js).

    Turbo Mount automatically detects window.Stimulus if available; otherwise, it initializes a new Stimulus application.

    Ensure your application.js imports your turbo-mount.js file.

    // app/javascript/turbo-mount.js
    import { TurboMount } from "turbo-mount";
    import { registerComponent } from "turbo-mount/react";
    import { HexColorPicker } from 'react-colorful';
    
    const turboMount = new TurboMount();
    
    registerComponent(turboMount, "HexColorPicker", HexColorPicker);
  5. Configure Turbo Mount with Importmaps

    main

    If you are using Importmaps instead of a bundler, you must pin the turbo-mount files and your chosen framework in config/importmap.rb.

    Note: Importmap-only mode is limited regarding JavaScript dependencies. For complex setups, a bundler like Vite is recommended.

    pin "turbo-mount", to: "turbo-mount.min.js"
    pin "turbo-mount/react", to: "turbo-mount/react.min.js"
    
    # Pin the framework
    bin/importmap pin react react-dom react-dom/client
    # or
    bin/importmap pin vue
    # or
    bin/importmap pin svelte
  6. How component registration and Stimulus linking works

    main

    The registerComponentsBase function automates the registration of components into TurboMount and attempts to link them to existing Stimulus controllers.

    Registration Logic

    1. Naming Convention: Component names are derived from filenames using normalizeFilenameToComponentName.
    2. Index Components: If a component is an index file (e.g., button/index.js), it is registered under its full path and also prepared to be registered under its shorter directory name (button).
    3. Precedence: Explicit component files (e.g., button.js) take precedence over index components (e.g., button/index.js). If button.js is already registered, the index component will not overwrite it.
    4. Stimulus Linking: For each component, the system generates potential Stimulus identifiers. If a matching Stimulus controller is found in the provided controllers array, the component is registered with that specific controllerConstructor. Otherwise, it is registered as a standard component.

    Usage Pattern

    To use this, you provide a plugin, the turboMount instance, an array of components (containing their filename and module), and an optional array of Stimulus controllers.

  7. How TurboMount handles Turbo Morphing

    main

    TurboMount includes built-in support for Turbo's morphing mechanism. It listens for the turbo:before-morph-element event.

    When an element with a data-controller containing turbo-mount is being morphed, TurboMount intercepts the event to ensure that component properties (stored in data-<controller-name>-props-value) are correctly transferred from the old element to the new element. This prevents loss of state/configuration during DOM updates.

  8. Install Turbo Mount via Rails generator

    main

    You can install Turbo Mount in a Rails application using its built-in generator. The generator handles creating the initializer, pinning dependencies to your package manager (npm, yarn, or importmap), and updating your JavaScript entrypoint.

    CLI Options

    OptionTypeDescription
    --frameworkstringThe framework you want to use (e.g., react). Must be one of the supported frameworks defined in the gem.
    --package-managerstringThe package manager to use (e.g., npm, yarn, importmap).
    --interactivebooleanWhether to prompt for optional installations (defaults to true).
    --verbosebooleanRun the generator in verbose mode (defaults to false).

    Usage

    To run the generator with specific options:

    rails generate turbo_mount:install --framework react --package-manager npm

    If you run the generator without arguments, it will enter an interactive mode asking you to select your framework (defaulting to react).

    rails generate turbo_mount:install --framework react --package-manager npm
  9. Specify a Custom Mount Target

    main

    If you do not want the component to mount to the root element of the Stimulus controller, use the block syntax in your view. Inside the block, use the data-[controller-name]-target="mount" attribute on the element where the component should be injected.

    <%= turbo_mount("HexColorPicker", props: {color: "#430"}) do |controller_name|
    %>
      <h3 class="text-lg">Color picker</h3>
      <div data-<%= controller_name %>-target="mount"></div>
    <% end %>
  10. Use the turbo_mount View Helper

    main

    Mount components in your Rails views using the turbo_mount helper. You can pass props and CSS class attributes.

    By default, the component mounts to the element generated by the helper. If you need to mount to a specific nested element, use the block syntax with the mount target (see Mount Target).

    <%= turbo_mount("HexColorPicker", props: {color: "#034"}, class: "mb-5") %>
  11. Prevent Turbo Mount assets from precompiling

    main

    If your Rails application uses a modern JavaScript bundling approach (like jsbundling-rails) instead of the standard Sprockets asset pipeline, you may want to prevent Turbo Mount from automatically adding its assets to the precompile list. You can do this by removing the engine's assets from the precompile list within a Rails initializer using config.after_initialize.

    # In a Rails initializer (e.g., config/initializers/turbo_mount.rb)
    Rails.application.configure do
      config.after_initialize do
        config.assets.precompile -= Turbo::Mount::Engine::PRECOMPILE_ASSETS
      end
    end