fix: use vite-plugin-symfony Stimulus loader and wrap react_component in div
Some checks failed
Build and Push Docker Images / Build app image (push) Failing after 17s
Build and Push Docker Images / Build database image (push) Successful in 8s
Build and Push Docker Images / Build node image (push) Failing after 17s

The @symfony/stimulus-bundle loader generates an empty controllers.js,
so Stimulus controllers from controllers.json (including ux-react) were
never registered. Switching to vite-plugin-symfony/stimulus/helpers
uses the virtual:symfony/controllers module that properly reads
controllers.json. Also wrap react_component() output in a <div> since
it only renders data-attributes, not a full HTML element.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
thibaud-leclere
2026-03-28 15:02:27 +01:00
parent ae9b0fdba7
commit 9cb5c6e2a5
8 changed files with 68 additions and 26 deletions

4
assets/bootstrap.js vendored
View File

@@ -1,4 +1,4 @@
import { startStimulusApp } from '@symfony/stimulus-bundle';
import { startStimulusApp } from 'vite-plugin-symfony/stimulus/helpers';
const app = startStimulusApp();
@@ -15,5 +15,5 @@ window.resolveReactComponent = (name) => {
.map(k => k.replace('./react/controllers/', '').replace('.jsx', ''));
throw new Error(`React controller "${name}" does not exist. Possible values: ${available.join(', ')}`);
}
return module.default;
return module;
};

View File

@@ -1,5 +1,11 @@
{
"controllers": {
"@symfony/ux-react": {
"react": {
"enabled": true,
"fetch": "eager"
}
},
"@symfony/ux-turbo": {
"turbo-core": {
"enabled": true,
@@ -9,12 +15,6 @@
"enabled": false,
"fetch": "eager"
}
},
"@symfony/ux-react": {
"react": {
"enabled": true,
"fetch": "eager"
}
}
},
"entrypoints": []

View File

@@ -1,3 +1,7 @@
when@dev:
pentatrion_vite:
proxy_origin: http://node:5173
pentatrion_vite:
default_build: app
builds:

View File

@@ -30,6 +30,14 @@ ENV APP_ENV=dev \
POSTGRES_USER=app \
POSTGRES_PASSWORD=pwd
###
# Composer install stage (provides vendor assets for node build)
###
FROM base AS composer-deps
COPY composer.json composer.lock symfony.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
###
# Node build stage (for prod assets)
###
@@ -37,6 +45,9 @@ FROM node:22-alpine AS node-build
WORKDIR /app
# Copy vendor UX assets so npm can resolve file: dependencies
COPY --from=composer-deps /app/vendor/symfony/ux-react/assets vendor/symfony/ux-react/assets
COPY package.json package-lock.json* ./
RUN npm install

56
package-lock.json generated
View File

@@ -15,6 +15,8 @@
"react-dom": "^19.0"
},
"devDependencies": {
"@symfony/stimulus-bridge": "^3.2.0 || ^4.0.0",
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
"@vitejs/plugin-react-swc": "^4.3.0",
"vite": "^6.0",
"vite-plugin-symfony": "^8.0"
@@ -961,7 +963,6 @@
"resolved": "https://registry.npmjs.org/@hotwired/stimulus-webpack-helpers/-/stimulus-webpack-helpers-1.0.1.tgz",
"integrity": "sha512-wa/zupVG0eWxRYJjC1IiPBdt3Lruv0RqGN+/DTMmUWUyMAEB27KXmVY6a8YpUVTM7QwVuaLNGW4EqDgrS2upXQ==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"@hotwired/stimulus": ">= 3.0"
}
@@ -1701,7 +1702,6 @@
"resolved": "https://registry.npmjs.org/@symfony/stimulus-bridge/-/stimulus-bridge-4.0.1.tgz",
"integrity": "sha512-+/kSQ4qFXMbZS+HjkhzOxwdN+60pMev7kzzDpQV/Tdm/iIWoxx5GDsVcdLaBb2783BVQHyrBP72JerF2SXTbTg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@hotwired/stimulus-webpack-helpers": "^1.0.1",
"@types/webpack-env": "^1.16.4",
@@ -1723,6 +1723,10 @@
"resolved": "vendor/symfony/ux-react/assets",
"link": true
},
"node_modules/@symfony/ux-turbo": {
"resolved": "vendor/symfony/ux-turbo/assets",
"link": true
},
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
@@ -1861,12 +1865,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/hotwired__turbo": {
"version": "8.0.9",
"resolved": "https://registry.npmjs.org/@types/hotwired__turbo/-/hotwired__turbo-8.0.9.tgz",
"integrity": "sha512-q2XWdObf8J+3V8fESKkd452Iy1A9ZFemQVGzKmxmZ02Clo1D/HEiX8bMQRmJ432RQ4sp24R0f7XQjHWNQC26XA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.14",
@@ -2069,7 +2079,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -2086,7 +2095,6 @@
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
@@ -2104,7 +2112,6 @@
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
@@ -2477,8 +2484,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/fast-glob": {
"version": "3.3.3",
@@ -2511,8 +2517,7 @@
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause",
"peer": true
"license": "BSD-3-Clause"
},
"node_modules/fastq": {
"version": "1.20.1",
@@ -2774,8 +2779,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/json5": {
"version": "2.2.3",
@@ -2795,7 +2799,6 @@
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz",
"integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 12.13.0"
}
@@ -3127,7 +3130,6 @@
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3260,7 +3262,6 @@
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
@@ -4683,6 +4684,29 @@
"optional": true
}
}
},
"vendor/symfony/ux-turbo/assets": {
"name": "@symfony/ux-turbo",
"version": "2.31.0",
"dev": true,
"license": "MIT",
"devDependencies": {
"@hotwired/stimulus": "^3.0.0",
"@hotwired/turbo": "^7.1.0 || ^8.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.1",
"@types/hotwired__turbo": "^8.0.4",
"jsdom": "^26.1.0",
"tslib": "^2.8.1",
"tsx": "^4.20.3",
"typescript": "^5.8.3",
"vitest": "^3.2.4"
},
"peerDependencies": {
"@hotwired/stimulus": "^3.0.0",
"@hotwired/turbo": "^7.1.0 || ^8.0"
}
}
}
}

View File

@@ -15,6 +15,8 @@
"react-dom": "^19.0"
},
"devDependencies": {
"@symfony/stimulus-bridge": "^3.2.0 || ^4.0.0",
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
"@vitejs/plugin-react-swc": "^4.3.0",
"vite": "^6.0",
"vite-plugin-symfony": "^8.0"

View File

@@ -1,9 +1,9 @@
{% extends 'base.html.twig' %}
{% block body %}
{{ react_component('GameGrid', {
<div {{ react_component('GameGrid', {
grid: grid,
width: width,
middle: middle,
}) }}
}) }}></div>
{% endblock %}

View File

@@ -23,6 +23,7 @@ export default defineConfig({
strictPort: true,
origin: 'http://localhost:5173',
cors: true,
allowedHosts: ['node'],
hmr: {
host: 'localhost',
port: 5173,