Symfony Stimulus Bundle

repository·3.x·Indexed 41 days ago

https://github.com/symfony/stimulus-bundle

Integration of @hotwired/stimulus into Symfony. This bundle bridges Symfony backend logic and Stimulus frontend controllers, providing Twig integration via stimulus_* functions, a StimulusHelper service for programmatically building data attributes in PHP, and support for eager and lazy controller loading. It integrates with AssetMapper, Webpack Encore, and Reprise (Vite/Rsbuild) to manage assets and controllers from custom directories and Symfony UX packages.

Tokens
5.3K
Snippets
15
Records
21
Agent score
51%

What's inside Symfony Stimulus Bundle

  1. Overview of StimulusBundle features

    3.x

    StimulusBundle provides integration between Symfony, Stimulus, and Symfony UX. It offers three primary capabilities:

    1. Twig Integration: Provides stimulus_* functions and filters to easily add Stimulus controllers, actions, and targets directly within Twig templates.
    2. Asset Management: Integrates with Symfony UX and AssetMapper for streamlined asset handling.
    3. Data Attribute Helper: Provides a helper service to programmatically build Stimulus data attributes, allowing you to use them within your Symfony services.
  2. Controller loading limitations with AssetMapper

    3.x
    Unlike Webpack Encore or Reprise, the startStimulusApp() function provided by @symfony/stimulus-bundle for AssetMapper does not support arguments. It always reads the full controllers.json file, meaning it does not currently support scoping controllers to specific parts of an application via the bundle's API.
  3. How Stimulus controllers are loaded from UX packages

    3.x

    When you install a Symfony UX package (e.g., @symfony/ux-chartjs), the bundle automatically makes its controllers available through a combination of Node package aliasing and a JSON manifest.

    1. Node Aliasing: The package is added to package.json as a file reference pointing to your vendor/ directory (e.g., '@symfony/ux-chartjs': 'file:vendor/symfony/ux-chartjs/assets').
    2. Manifest Registration: The package's controllers are added to assets/controllers.json.
    3. Automatic Loading: Your assets/stimulus_bootstrap.js (configured for AssetMapper, Encore, or Reprise) automatically registers controllers found in assets/controllers/ AND those listed in assets/controllers.json.

    Example: Using a UX controller in Twig If a package provides a controller named @symfony/ux-chartjs/chart, it is internally mapped to symfony--ux-chartjs--chart. You can use the original name in Twig with the stimulus_controller() helper, and it will be normalized:

    <div {{ stimulus_controller('@symfony/ux-chartjs/chart') }}>
        <!-- Renders as: <div data-controller="symfony--ux-chartjs--chart"> -->
    </div>
  4. Setup Stimulus with AssetMapper

    3.x

    When using Symfony AssetMapper, the bundle integrates via importmap.php. The Flex recipe adds @symfony/stimulus-bundle and @hotwired/stimulus to your importmap.

    Your assets/stimulus_bootstrap.js should use startStimulusApp from the bundle, which dynamically loads both your custom controllers and those registered in controllers.json.

    importmap.php entries:

    return [
        '@symfony/stimulus-bundle' => [
            'path' => '@symfony/stimulus-bundle/loader.js',
        ],
        '@hotwired/stimulus' => [
            'version' => '3.2.2',
        ],
    ];

    assets/stimulus_bootstrap.js:

    import { startStimulusApp } from '@symfony/stimulus-bundle';
    
    const app = startStimulusApp();
    // assets/stimulus_bootstrap.js
    import { startStimulusApp } from '@symfony/stimulus-bundle';
    
    const app = startStimulusApp();
  5. Create and activate a custom Stimulus controller

    3.x

    Custom Stimulus controllers should be placed in the assets/controllers/ directory.

    1. Create the controller: Define your class extending Controller from @hotwired/stimulus.
    2. Activate in HTML: Use the data-controller attribute to link the HTML element to your controller. For example, a file named assets/controllers/hello_controller.js is activated using data-controller="hello".

    Alternatively, you can use the Twig helper stimulus_controller('hello') to render the attribute.

    import { Controller } from '@hotwired/stimulus';
    
    export default class extends Controller {
        connect() {
            this.element.textContent = 'Hello Stimulus!';
        }
    }
    <div data-controller="hello">
       ...
    </div>
    
    <!-- Using Twig helper -->
    <div {{ stimulus_controller('hello') }}>
        ...
    </div>
  6. Scope Stimulus controllers with Reprise

    3.x

    By default, all registered controllers load on every page. To prevent eager loading of controllers from one area (e.g., admin) into another (e.g., public site), you can use Reprise to create scoped builds.

    To achieve this, give the specific area its own build configuration and point the Reprise plugin to a dedicated controllers directory and a specific controllers.json file.

    After configuring the build, register it on the PHP side under reprise.builds. This allows Symfony Twig helpers to target that specific build using the build argument.

    // vite.config.admin.ts  -- run with `vite build --config vite.config.admin.ts`
    import { defineConfig } from 'vite'
    import Symfony from '@symfony/reprise/vite'
    
    export default defineConfig({
      input: {
        admin: './assets/admin.js',
      },
      plugins: [
        Symfony({
          outputPath: 'public/admin-build',
          publicPath: '/admin-build/',
          stimulus: {
            controllersJson: 'assets/admin/controllers.json',
            controllersDir: 'assets/admin/controllers',
          },
        }),
      ],
    })
    // assets/admin.js
    import { startStimulusApp } from '@symfony/reprise/stimulus'
    
    const app = startStimulusApp()
  7. Scope Stimulus controllers with Webpack Encore

    3.x

    If you are using Webpack Encore, you can scope controllers by passing a Webpack context to startStimulusApp(). This allows an entry point to load only the controllers within a specific directory.

    Single Scoped Entry Point

    To load only controllers under a specific path (e.g., ./controllers/admin), pass a require.context to startStimulusApp. If you rely on lazy loading, ensure you include the @symfony/stimulus-bridge/lazy-controller-loader! prefix in the context path.

    Loading Multiple Contexts into One Application

    If you have shared controllers and area-specific controllers, you can load multiple contexts into a single application instance using app.load() and definitionsFromContext from @hotwired/stimulus-webpack-helpers.

    // Single scoped entry point
    import { startStimulusApp } from '@symfony/stimulus-bridge';
    
    export const app = startStimulusApp(require.context(
        '@symfony/stimulus-bridge/lazy-controller-loader!./controllers/admin',
        true,
        /\.[jt]sx?$/
    ));
    // Loading multiple contexts
    import { startStimulusApp } from '@symfony/stimulus-bridge';
    import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers';
    
    const app = startStimulusApp();
    
    app.load(definitionsFromContext(require.context('./controllers/common', true)));
    app.load(definitionsFromContext(require.context('./controllers/admin', true)));
  8. Install StimulusBundle

    3.x

    To integrate Stimulus with Symfony, first ensure you have an asset handling system installed (such as AssetMapper, Webpack Encore, or Reprise). Then, install the bundle via Composer:

    $ composer require symfony/stimulus-bundle

    If you are using Symfony Flex, the necessary files will be updated automatically. If you are using Webpack Encore, remember to install your assets (e.g., npm install) and restart the Encore process.

  9. Setup Stimulus with Webpack Encore

    3.x

    For Webpack Encore users, the bundle requires @symfony/stimulus-bridge.

    1. Install the bridge:

      npm install --save-dev @symfony/stimulus-bridge
    2. Update webpack.config.js to enable the Stimulus bridge:

      .enableStimulusBridge('./assets/controllers.json')
    3. Configure assets/stimulus_bootstrap.js to use the bridge and load controllers via require.context:

      import { startStimulusApp } from '@symfony/stimulus-bridge';
      
      export const app = startStimulusApp(require.context(
          '@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
          true,
          /\.[jt]sx?$/
      ));
    $ npm install --save-dev @symfony/stimulus-bridge
  10. Setup Stimulus with Reprise (Vite/Rsbuild)

    3.x

    If you are using Reprise, you must point the Reprise plugin to your controllers.json file in your Vite or Rsbuild configuration.

    vite.config.js:

    import Symfony from '@symfony/reprise/vite';
    
    export default defineConfig({
        plugins: [
            Symfony({
                stimulus: './assets/controllers.json',
            }),
        ],
    });

    assets/stimulus_bootstrap.js:

    import { startStimulusApp } from '@symfony/reprise/stimulus';
    
    const app = startStimulusApp();
    // vite.config.js
    import Symfony from '@symfony/reprise/vite';
    
    export default defineConfig({
        plugins: [
            Symfony({
                stimulus: './assets/controllers.json',
            }),
        ],
    });
  11. How eager and lazy controller loading works

    3.x

    The Stimulus bundle distinguishes between two types of controller loading to optimize performance:

    Eager Controllers

    Eager controllers are registered immediately when loadControllers() is called. These are typically controllers that are required on almost every page or are part of the core application logic.

    Lazy Controllers

    Lazy controllers are loaded only when they are actually needed in the DOM. The StimulusLazyControllerHandler manages this via two mechanisms:

    1. Initial Scan: It scans the existing DOM for data-controller attributes and triggers the loading of the corresponding lazy modules.
    2. Mutation Observation: It sets up a MutationObserver to watch for new elements being added to the DOM or existing elements having their data-controller attributes modified (common in applications using Turbo or Live Components).

    Once a lazy controller has been requested and its module is loaded, the loader disconnects the observer if no more lazy controllers remain in the registry to prevent unnecessary performance overhead during frequent DOM mutations.