Skip to content

Support Symfony's Stimulus Bridge#431

Open
marcmorente wants to merge 17 commits intomarcoroth:mainfrom
marcmorente:symfony-stimulus-bridge-support
Open

Support Symfony's Stimulus Bridge#431
marcmorente wants to merge 17 commits intomarcoroth:mainfrom
marcmorente:symfony-stimulus-bridge-support

Conversation

@marcmorente
Copy link

@marcmorente marcmorente commented Dec 31, 2025

This PR resolves #67

Problem

Stimulus controllers in Symfony projects were not being detected by stimulus-parser, causing several issues in stimulus-lsp:

  • Missing controllers in LSP diagnostics — False warnings about undefined controller identifiers
  • No autocomplete — Controller names not suggested when typing data-controller="..."
  • Empty "Stimulus Controllers" view — No controllers displayed in VS Code sidebar
  • Incorrect code actions — Unable to create new controllers or navigate to existing ones

Root Cause

Symfony supports two different ways to integrate Stimulus, and the parser needed to support both:

Pattern 1: @symfony/stimulus-bridge (Webpack Encore)

// assets/bootstrap.js
import { startStimulusApp } from '@symfony/stimulus-bridge';

export const app = startStimulusApp(require.context(
    '@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
    true,
    /\.[jt]sx?$/
));

Pattern 2: @symfony/stimulus-bundle (AssetMapper)

// assets/bootstrap.js
import { startStimulusApp } from '@symfony/stimulus-bundle';

startStimulusApp();

The parser originally only supported Pattern 1. Pattern 2 uses a simpler startStimulusApp() call without the require.context() wrapper, which went unrecognized.

Solution

Added comprehensive support for both Symfony Stimulus integration patterns:

For @symfony/stimulus-bridge:

  1. Recognize @symfony/stimulus-bridge as a helper package
  2. Detect startStimulusApp() calls with require.context() using the @symfony/stimulus-bridge/lazy-controller-loader! prefix
  3. Register controllers with the correct stimulus-loading-lazy load mode

For @symfony/stimulus-bundle (AssetMapper):

  1. Recognize @symfony/stimulus-bundle as a helper package
  2. Detect AssetMapper projects by checking for importmap.php or config/packages/stimulus.yaml
  3. Handle startStimulusApp() calls without context arguments
  4. Automatically resolve controllers from the controllers/ subdirectory

Result

After this fix, stimulus-lsp correctly:

  • Detects all Stimulus controllers in both Webpack Encore and AssetMapper projects
  • Shows controllers in the "Stimulus Controllers" view
  • Provides accurate diagnostics (no false positives)
  • Offers proper autocomplete for controller identifiers
  • Enables code actions for creating/navigating to controllers

Key Differences at a Glance

Feature @symfony/stimulus-bridge @symfony/stimulus-bundle
Symfony integration Webpack Encore AssetMapper
Build tool Webpack PHP-native (no build step)
Bootstrap pattern startStimulusApp(require.context(...)) startStimulusApp()
Detection method Package + lazy loader prefix Package + file indicators
Controllers directory ./controllers ./controllers

This prevents the package from being ignored during package analysis.
Add analyzeStimulusBridgeLazyLoading() method to parse startStimulusApp()
calls with require.context using the @symfony/stimulus-bridge/lazy-controller-loader!
prefix pattern.
Add fixture project with bootstrap.js and controller demonstrating the
Symfony Stimulus Bridge lazy loading pattern.
Add integration test verifying controllers are detected with
stimulus-loading-lazy load mode when using the Symfony Stimulus Bridge.
This package is used by Symfony projects with AssetMapper to bootstrap
Stimulus applications. Adding it to helperPackages enables detection
of controllers index files that import from this package.
Detects if a project uses Symfony AssetMapper by checking for the
presence of importmap.php or config/packages/stimulus.yaml files.

Uses lazy initialization with existsSync for efficient repeated access.
…Index

- Added hasStimulusBundleImport to detect imports from @symfony/stimulus-bundle
- Updated isStimulusControllersIndex to return true for files with
  @symfony/stimulus-bundle import when project uses AssetMapper

This allows detection of AssetMapper bootstrap files like:
  import { startStimulusApp } from '@symfony/stimulus-bundle'
  startStimulusApp()
AssetMapper uses a simpler pattern than Webpack Encore:
  - Webpack: startStimulusApp(require.context(...))
  - AssetMapper: startStimulusApp()

This change:
- Detects AssetMapper projects via Project.isAssetMapper
- Handles startStimulusApp() calls without context arguments
- Defaults controller root to 'controllers/' subdirectory

Also removed unused fileExists import and redundant hasStimulusBundle check.
This fixture tests AssetMapper support with:
- assets/bootstrap.js: Uses startStimulusApp() without context (AssetMapper pattern)
- assets/controllers/hello_controller.js: Sample Stimulus controller

The fixture is set up to use a mock @symfony/stimulus-bundle package
since we can't install the real package in test fixtures.
The symfony-asset-mapper fixture needs to import from @symfony/stimulus-bundle
but can't use the real package. This change creates a minimal mock package
that exports the startStimulusApp function needed for testing.

The mock package is created at:
  test/fixtures/symfony-asset-mapper/node_modules/@symfony/stimulus-bundle/

With:
- package.json with name, version, and main entry point
- loader.js exporting startStimulusApp function
Tests that:
- AssetMapper project is correctly detected
- Controllers index file (bootstrap.js) is found
- The hello controller is registered with stimulus-loading-lazy mode
- Controller root is set to assets/controllers
…plicationFileImport

The original code had a subtle bug where undefined === undefined evaluates to true,
causing all files to incorrectly return true for hasResolvedStimulusApplicationFileImport
when applicationFile was not defined.

This fix adds an early return when applicationFile is undefined.
The AssetMapper detection previously required importmap.php or config/packages/stimulus.yaml files.
However, projects using @symfony/stimulus-bundle should be detected even without these files present.

This change:
- Adds check for hasStimulusBundleImport to the early return guard
- Updates the AssetMapper branch to trigger when hasStimulusBundle is true
- Allows detection of AssetMapper projects that only import @symfony/stimulus-bundle
@@ -0,0 +1,3 @@
import { startStimulusApp } from '@symfony/stimulus-bundle'

const app = startStimulusApp()

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable app.
@marcoroth
Copy link
Owner

Thank you so much @marcmorente! I haven't forgotten about this and I'm going to take a look! 🙏🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Symfony's Stimulus Bridge

2 participants