├── assets ├── dist │ ├── components.js │ ├── components.d.ts │ ├── register_controller.d.ts │ ├── loader.d.ts │ ├── render_controller.d.ts │ ├── loader.js │ ├── render_controller.js │ └── register_controller.js ├── LICENSE ├── README.md └── package.json ├── src ├── VueBundle.php ├── Twig │ └── VueComponentExtension.php ├── AssetMapper │ └── VueControllerLoaderAssetCompiler.php └── DependencyInjection │ └── VueExtension.php ├── LICENSE ├── CHANGELOG.md ├── README.md └── composer.json /assets/dist/components.js: -------------------------------------------------------------------------------- 1 | const components = {}; 2 | export { 3 | components 4 | }; 5 | -------------------------------------------------------------------------------- /assets/dist/components.d.ts: -------------------------------------------------------------------------------- 1 | import { Component } from 'vue'; 2 | 3 | interface ComponentCollection { 4 | [key: string]: Component; 5 | } 6 | declare const components: ComponentCollection; 7 | 8 | export { type ComponentCollection, components }; 9 | -------------------------------------------------------------------------------- /assets/dist/register_controller.d.ts: -------------------------------------------------------------------------------- 1 | import { Component } from 'vue'; 2 | 3 | declare global { 4 | function resolveVueComponent(name: string): Component; 5 | interface Window { 6 | resolveVueComponent(name: string): Component; 7 | } 8 | } 9 | declare function registerVueControllerComponents(context: __WebpackModuleApi.RequireContext): void; 10 | 11 | export { registerVueControllerComponents }; 12 | -------------------------------------------------------------------------------- /assets/dist/loader.d.ts: -------------------------------------------------------------------------------- 1 | import { Component } from 'vue'; 2 | import { ComponentCollection } from './components.js'; 3 | 4 | declare global { 5 | function resolveVueComponent(name: string): Component; 6 | interface Window { 7 | resolveVueComponent(name: string): Component; 8 | } 9 | } 10 | declare function registerVueControllerComponents(vueControllers?: ComponentCollection): void; 11 | 12 | export { registerVueControllerComponents }; 13 | -------------------------------------------------------------------------------- /assets/dist/render_controller.d.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@hotwired/stimulus'; 2 | import { App } from 'vue'; 3 | 4 | declare class export_default extends Controller; 6 | }> { 7 | private props; 8 | private app; 9 | readonly componentValue: string; 10 | readonly propsValue: Record | null | undefined; 11 | static values: { 12 | component: StringConstructor; 13 | props: ObjectConstructor; 14 | }; 15 | connect(): void; 16 | disconnect(): void; 17 | private dispatchEvent; 18 | } 19 | 20 | export { export_default as default }; 21 | -------------------------------------------------------------------------------- /assets/dist/loader.js: -------------------------------------------------------------------------------- 1 | import { components } from "./components.js"; 2 | function registerVueControllerComponents(vueControllers = components) { 3 | function loadComponent(name) { 4 | const component = vueControllers[name]; 5 | if (typeof component === "undefined") { 6 | const possibleValues = Object.keys(vueControllers).length > 0 ? Object.keys(vueControllers).join(", ") : "none"; 7 | throw new Error(`Vue controller "${name}" does not exist. Possible values: ${possibleValues}`); 8 | } 9 | return component; 10 | } 11 | window.resolveVueComponent = (name) => { 12 | return loadComponent(name); 13 | }; 14 | } 15 | export { 16 | registerVueControllerComponents 17 | }; 18 | -------------------------------------------------------------------------------- /src/VueBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\UX\Vue; 13 | 14 | use Symfony\Component\HttpKernel\Bundle\Bundle; 15 | 16 | /** 17 | * @author Titouan Galopin 18 | * @author Thibault RICHARD 19 | * 20 | * @final 21 | */ 22 | class VueBundle extends Bundle 23 | { 24 | public function getPath(): string 25 | { 26 | return \dirname(__DIR__); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /assets/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 2.30 4 | 5 | - Ensure compatibility with PHP 8.5 6 | 7 | ## 2.29.0 8 | 9 | - Add Symfony 8 support 10 | 11 | ## 2.13.2 12 | 13 | - Revert "Change JavaScript package to `type: module`" 14 | 15 | ## 2.13.0 16 | 17 | - Add Symfony 7 support. 18 | - Change JavaScript package to `type: module` 19 | 20 | ## 2.9.0 21 | 22 | - Replace `symfony/webpack-encore-bundle` by `symfony/stimulus-bundle` in dependencies 23 | 24 | - Add support for symfony/asset-mapper 25 | 26 | - Minimum PHP version is now 8.1 27 | 28 | ## 2.7.0 29 | 30 | - Add `assets/src` to `.gitattributes` to exclude source TypeScript files from 31 | installing. 32 | 33 | - TypeScript types are now included. 34 | 35 | ## 2.6.0 36 | 37 | - [BC BREAK] The `assets/` directory was moved from `Resources/assets/` to `assets/`. Make 38 | sure the path in your `package.json` file is updated accordingly. 39 | 40 | - The directory structure of the bundle was updated to match modern best-practices. 41 | 42 | ## 2.5 43 | 44 | - Added support for lazily-loaded Vue components - #482. 45 | 46 | - Added `vue:before-mount` JavaScript event - #444. 47 | 48 | ## 2.4 49 | 50 | - Component added 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symfony UX Vue.js 2 | 3 | Symfony UX Vue integrates [Vue.js](https://vuejs.org/) into Symfony applications. 4 | It provides tools to render Vue.js v3 components from Twig. 5 | 6 | **This repository is a READ-ONLY sub-tree split**. See 7 | https://github.com/symfony/ux to create issues or submit pull requests. 8 | 9 | ## Sponsor 10 | 11 | The Symfony UX packages are [backed][1] by [Mercure.rocks][2]. 12 | 13 | Create real-time experiences in minutes! Mercure.rocks provides a realtime API service 14 | that is tightly integrated with Symfony: create UIs that update in live with UX Turbo, 15 | send notifications with the Notifier component, expose async APIs with API Platform and 16 | create low level stuffs with the Mercure component. We maintain and scale the complex 17 | infrastructure for you! 18 | 19 | Help Symfony by [sponsoring][3] its development! 20 | 21 | ## Resources 22 | 23 | - [Documentation](https://symfony.com/bundles/ux-vue/current/index.html) 24 | - [Report issues](https://github.com/symfony/ux/issues) and 25 | [send Pull Requests](https://github.com/symfony/ux/pulls) 26 | in the [main Symfony UX repository](https://github.com/symfony/ux) 27 | 28 | [1]: https://symfony.com/backers 29 | [2]: https://mercure.rocks 30 | [3]: https://symfony.com/sponsor 31 | -------------------------------------------------------------------------------- /assets/dist/render_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus"; 2 | import { createApp } from "vue"; 3 | class render_controller_default extends Controller { 4 | connect() { 5 | this.props = this.propsValue ?? null; 6 | this.dispatchEvent("connect", { componentName: this.componentValue, props: this.props }); 7 | const component = window.resolveVueComponent(this.componentValue); 8 | this.app = createApp(component, this.props); 9 | if (this.element.__vue_app__ !== void 0) { 10 | this.element.__vue_app__.unmount(); 11 | } 12 | this.dispatchEvent("before-mount", { 13 | componentName: this.componentValue, 14 | component, 15 | props: this.props, 16 | app: this.app 17 | }); 18 | this.app.mount(this.element); 19 | this.dispatchEvent("mount", { 20 | componentName: this.componentValue, 21 | component, 22 | props: this.props 23 | }); 24 | } 25 | disconnect() { 26 | this.app.unmount(); 27 | this.dispatchEvent("unmount", { 28 | componentName: this.componentValue, 29 | props: this.props 30 | }); 31 | } 32 | dispatchEvent(name, payload) { 33 | this.dispatch(name, { detail: payload, prefix: "vue" }); 34 | } 35 | } 36 | render_controller_default.values = { 37 | component: String, 38 | props: Object 39 | }; 40 | export { 41 | render_controller_default as default 42 | }; 43 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # @symfony/ux-vue 2 | 3 | JavaScript assets of the [symfony/ux-vue](https://packagist.org/packages/symfony/ux-vue) PHP package. 4 | 5 | ## Installation 6 | 7 | This npm package is **reserved for advanced users** who want to decouple their JavaScript dependencies from their PHP dependencies (e.g., when building Docker images, running JavaScript-only pipelines, etc.). 8 | 9 | We **strongly recommend not installing this package directly**, but instead install the PHP package [symfony/ux-vue](https://packagist.org/packages/symfony/ux-vue) in your Symfony application with [Flex](https://github.com/symfony/flex) enabled. 10 | 11 | If you still want to install this package directly, please make sure its version exactly matches [symfony/ux-vue](https://packagist.org/packages/symfony/ux-vue) PHP package version: 12 | ```shell 13 | composer require symfony/ux-vue:2.23.0 14 | npm add @symfony/ux-vue@2.23.0 15 | ``` 16 | 17 | **Tip:** Your `package.json` file will be automatically modified by [Flex](https://github.com/symfony/flex) when installing or upgrading a PHP package. To prevent this behavior, ensure to **use at least Flex 1.22.0 or 2.5.0**, and run `composer config --json "extra.symfony/flex.synchronize_package_json" false`. 18 | 19 | ## Resources 20 | 21 | - [Documentation](https://symfony.com/bundles/ux-vue/current/index.html) 22 | - [Report issues](https://github.com/symfony/ux/issues) and 23 | [send Pull Requests](https://github.com/symfony/ux/pulls) 24 | in the [main Symfony UX repository](https://github.com/symfony/ux) 25 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/ux-vue", 3 | "type": "symfony-bundle", 4 | "description": "Integration of Vue.js in Symfony", 5 | "keywords": [ 6 | "symfony-ux" 7 | ], 8 | "homepage": "https://symfony.com", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Titouan Galopin", 13 | "email": "galopintitouan@gmail.com" 14 | }, 15 | { 16 | "name": "Thibault Richard", 17 | "email": "thibault.richard62@gmail.com" 18 | }, 19 | { 20 | "name": "Symfony Community", 21 | "homepage": "https://symfony.com/contributors" 22 | } 23 | ], 24 | "autoload": { 25 | "psr-4": { 26 | "Symfony\\UX\\Vue\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Symfony\\UX\\Vue\\Tests\\": "tests/" 32 | } 33 | }, 34 | "require": { 35 | "php": ">=8.1", 36 | "symfony/stimulus-bundle": "^2.9.1" 37 | }, 38 | "require-dev": { 39 | "symfony/asset-mapper": "^6.3|^7.0|^8.0", 40 | "symfony/finder": "^5.4|^6.0|^7.0|^8.0", 41 | "symfony/framework-bundle": "^5.4|^6.0|^7.0|^8.0", 42 | "symfony/phpunit-bridge": "^5.4|^6.0|^7.0|^8.0", 43 | "symfony/twig-bundle": "^5.4|^6.0|^7.0|^8.0", 44 | "symfony/var-dumper": "^5.4|^6.0|^7.0|^8.0" 45 | }, 46 | "extra": { 47 | "thanks": { 48 | "name": "symfony/ux", 49 | "url": "https://github.com/symfony/ux" 50 | } 51 | }, 52 | "minimum-stability": "dev" 53 | } 54 | -------------------------------------------------------------------------------- /assets/dist/register_controller.js: -------------------------------------------------------------------------------- 1 | import { defineAsyncComponent } from "vue"; 2 | function registerVueControllerComponents(context) { 3 | const vueControllers = context.keys().reduce( 4 | (acc, key) => { 5 | acc[key] = void 0; 6 | return acc; 7 | }, 8 | {} 9 | ); 10 | function loadComponent(name) { 11 | const componentPath = `./${name}.vue`; 12 | if (!(componentPath in vueControllers)) { 13 | const possibleValues = Object.keys(vueControllers).map((key) => key.replace("./", "").replace(".vue", "")); 14 | throw new Error(`Vue controller "${name}" does not exist. Possible values: ${possibleValues.join(", ")}`); 15 | } 16 | if (typeof vueControllers[componentPath] === "undefined") { 17 | const module = context(componentPath); 18 | if (module.default) { 19 | vueControllers[componentPath] = module.default; 20 | } else if (module instanceof Promise) { 21 | vueControllers[componentPath] = defineAsyncComponent( 22 | () => new Promise((resolve, reject) => { 23 | module.then((resolvedModule) => { 24 | if (resolvedModule.default) { 25 | resolve(resolvedModule.default); 26 | } else { 27 | reject( 28 | new Error(`Cannot find default export in async Vue controller "${name}".`) 29 | ); 30 | } 31 | }).catch(reject); 32 | }) 33 | ); 34 | } else { 35 | throw new Error(`Vue controller "${name}" does not exist.`); 36 | } 37 | } 38 | return vueControllers[componentPath]; 39 | } 40 | window.resolveVueComponent = (name) => { 41 | return loadComponent(name); 42 | }; 43 | } 44 | export { 45 | registerVueControllerComponents 46 | }; 47 | -------------------------------------------------------------------------------- /src/Twig/VueComponentExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\UX\Vue\Twig; 13 | 14 | use Symfony\UX\StimulusBundle\Helper\StimulusHelper; 15 | use Symfony\WebpackEncoreBundle\Twig\StimulusTwigExtension; 16 | use Twig\Extension\AbstractExtension; 17 | use Twig\TwigFunction; 18 | 19 | /** 20 | * @author Titouan Galopin 21 | * @author Thibault RICHARD 22 | * 23 | * @final 24 | */ 25 | class VueComponentExtension extends AbstractExtension 26 | { 27 | private $stimulusHelper; 28 | 29 | /** 30 | * @param $stimulus StimulusHelper 31 | */ 32 | public function __construct(StimulusHelper|StimulusTwigExtension $stimulus) 33 | { 34 | if ($stimulus instanceof StimulusTwigExtension) { 35 | trigger_deprecation('symfony/ux-vue', '2.9', 'Passing an instance of "%s" to "%s" is deprecated, pass an instance of "%s" instead.', StimulusTwigExtension::class, __CLASS__, StimulusHelper::class); 36 | $stimulus = new StimulusHelper(null); 37 | } 38 | 39 | $this->stimulusHelper = $stimulus; 40 | } 41 | 42 | public function getFunctions(): array 43 | { 44 | return [ 45 | new TwigFunction('vue_component', [$this, 'renderVueComponent'], ['is_safe' => ['html_attr']]), 46 | ]; 47 | } 48 | 49 | public function renderVueComponent(string $componentName, array $props = []): string 50 | { 51 | $params = ['component' => $componentName]; 52 | if ($props) { 53 | $params['props'] = $props; 54 | } 55 | 56 | $stimulusAttributes = $this->stimulusHelper->createStimulusAttributes(); 57 | $stimulusAttributes->addController('@symfony/ux-vue/vue', $params); 58 | 59 | return (string) $stimulusAttributes; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@symfony/ux-vue", 3 | "description": "Integration of Vue.js in Symfony", 4 | "license": "MIT", 5 | "version": "2.31.0", 6 | "keywords": [ 7 | "symfony-ux" 8 | ], 9 | "homepage": "https://ux.symfony.com/vue", 10 | "repository": "https://github.com/symfony/ux-vue", 11 | "type": "module", 12 | "files": [ 13 | "dist" 14 | ], 15 | "main": "dist/register_controller.js", 16 | "types": "dist/register_controller.d.ts", 17 | "scripts": { 18 | "build": "tsx ../../../bin/build_package.ts .", 19 | "watch": "tsx ../../../bin/build_package.ts . --watch", 20 | "test": "pnpm run test:unit && pnpm run test:browser", 21 | "test:unit": "../../../bin/unit_test_package.sh .", 22 | "test:browser": "playwright test", 23 | "test:browser:ui": "playwright test --ui", 24 | "check": "biome check", 25 | "ci": "biome ci" 26 | }, 27 | "symfony": { 28 | "controllers": { 29 | "vue": { 30 | "main": "dist/render_controller.js", 31 | "webpackMode": "eager", 32 | "fetch": "eager", 33 | "enabled": true 34 | } 35 | }, 36 | "importmap": { 37 | "@hotwired/stimulus": "^3.0.0", 38 | "vue": { 39 | "package": "vue/dist/vue.esm-bundler.js", 40 | "version": "^3.0" 41 | }, 42 | "@symfony/ux-vue": "path:%PACKAGE%/dist/loader.js" 43 | } 44 | }, 45 | "peerDependencies": { 46 | "@hotwired/stimulus": "^3.0.0", 47 | "vue": "^3.0" 48 | }, 49 | "devDependencies": { 50 | "@hotwired/stimulus": "^3.0.0", 51 | "@testing-library/dom": "^10.4.0", 52 | "@testing-library/jest-dom": "^6.6.3", 53 | "@testing-library/user-event": "^14.6.1", 54 | "@types/webpack-env": "^1.16", 55 | "@vitejs/plugin-vue": "^6.0.0", 56 | "jsdom": "^26.1.0", 57 | "tslib": "^2.8.1", 58 | "tsx": "^4.20.3", 59 | "typescript": "^5.8.3", 60 | "vitest": "^3.2.4", 61 | "vue": "^3.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/AssetMapper/VueControllerLoaderAssetCompiler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\UX\Vue\AssetMapper; 13 | 14 | use Symfony\Component\AssetMapper\AssetDependency; 15 | use Symfony\Component\AssetMapper\AssetMapperInterface; 16 | use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface; 17 | use Symfony\Component\AssetMapper\MappedAsset; 18 | use Symfony\Component\Filesystem\Path; 19 | use Symfony\Component\Finder\Finder; 20 | 21 | /** 22 | * Compiles the components.js file to dynamically import the Vue controller components. 23 | * 24 | * @author Ryan Weaver 25 | */ 26 | class VueControllerLoaderAssetCompiler implements AssetCompilerInterface 27 | { 28 | public function __construct( 29 | private string $controllerPath, 30 | private array $nameGlobs, 31 | ) { 32 | } 33 | 34 | public function supports(MappedAsset $asset): bool 35 | { 36 | return $asset->sourcePath === realpath(__DIR__.'/../../assets/dist/components.js'); 37 | } 38 | 39 | public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string 40 | { 41 | $importLines = []; 42 | $componentParts = []; 43 | foreach ($this->findControllerAssets($assetMapper) as $name => $mappedAsset) { 44 | // @legacy: backwards compatibility with Symfony 6.3 45 | if (class_exists(AssetDependency::class)) { 46 | $controllerPublicPath = $mappedAsset->publicPathWithoutDigest; 47 | $loaderPublicPath = $asset->publicPathWithoutDigest; 48 | $relativeImportPath = Path::makeRelative($controllerPublicPath, \dirname($loaderPublicPath)); 49 | } else { 50 | $relativeImportPath = Path::makeRelative($mappedAsset->sourcePath, \dirname($asset->sourcePath)); 51 | } 52 | 53 | $controllerNameForVariable = \sprintf('component_%s', \count($componentParts)); 54 | 55 | $importLines[] = \sprintf( 56 | "import %s from '%s';", 57 | $controllerNameForVariable, 58 | $relativeImportPath 59 | ); 60 | $componentParts[] = \sprintf('"%s": %s', Path::normalize($name), $controllerNameForVariable); 61 | } 62 | 63 | $importCode = implode("\n", $importLines); 64 | $componentsJson = \sprintf('{%s}', implode(', ', $componentParts)); 65 | 66 | return <<controllerPath)) { 82 | return []; 83 | } 84 | 85 | $finder = new Finder(); 86 | $finder->in($this->controllerPath) 87 | ->files() 88 | ->name($this->nameGlobs) 89 | ; 90 | $assets = []; 91 | foreach ($finder as $file) { 92 | $asset = $assetMapper->getAssetFromSourcePath($file->getRealPath()); 93 | 94 | if (null === $asset) { 95 | throw new \LogicException(\sprintf('Could not find an asset mapper path for the Vue controller file "%s".', $file->getRealPath())); 96 | } 97 | 98 | $name = $file->getRelativePathname(); 99 | $name = substr($name, 0, -\strlen($file->getExtension()) - 1); 100 | 101 | $assets[$name] = $asset; 102 | } 103 | 104 | return $assets; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/DependencyInjection/VueExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\UX\Vue\DependencyInjection; 13 | 14 | use Symfony\Component\AssetMapper\AssetMapperInterface; 15 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 16 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 17 | use Symfony\Component\Config\Definition\ConfigurationInterface; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | use Symfony\Component\DependencyInjection\Definition; 20 | use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; 21 | use Symfony\Component\DependencyInjection\Reference; 22 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 23 | use Symfony\UX\Vue\AssetMapper\VueControllerLoaderAssetCompiler; 24 | use Symfony\UX\Vue\Twig\VueComponentExtension; 25 | 26 | /** 27 | * @author Titouan Galopin 28 | * @author Thibault RICHARD 29 | * 30 | * @internal 31 | */ 32 | class VueExtension extends Extension implements PrependExtensionInterface, ConfigurationInterface 33 | { 34 | public function load(array $configs, ContainerBuilder $container): void 35 | { 36 | $configuration = $this->getConfiguration($configs, $container); 37 | $config = $this->processConfiguration($configuration, $configs); 38 | 39 | $container 40 | ->setDefinition('twig.extension.vue', new Definition(VueComponentExtension::class)) 41 | ->setArgument(0, new Reference('stimulus.helper')) 42 | ->addTag('twig.extension') 43 | ->setPublic(false) 44 | ; 45 | 46 | $container->setDefinition('vue.asset_mapper.vue_controller_loader_compiler', new Definition(VueControllerLoaderAssetCompiler::class)) 47 | ->setArguments([ 48 | $config['controllers_path'], 49 | $config['name_glob'], 50 | ]) 51 | // run before the core JavaScript compiler 52 | ->addTag('asset_mapper.compiler', ['priority' => 100]) 53 | ; 54 | } 55 | 56 | public function prepend(ContainerBuilder $container): void 57 | { 58 | if (!$this->isAssetMapperAvailable($container)) { 59 | return; 60 | } 61 | 62 | $container->prependExtensionConfig('framework', [ 63 | 'asset_mapper' => [ 64 | 'paths' => [ 65 | __DIR__.'/../../assets/dist' => '@symfony/ux-vue', 66 | ], 67 | ], 68 | ]); 69 | } 70 | 71 | public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface 72 | { 73 | return $this; 74 | } 75 | 76 | public function getConfigTreeBuilder(): TreeBuilder 77 | { 78 | $treeBuilder = new TreeBuilder('vue'); 79 | $rootNode = $treeBuilder->getRootNode(); 80 | \assert($rootNode instanceof ArrayNodeDefinition); 81 | 82 | $rootNode 83 | ->children() 84 | ->scalarNode('controllers_path') 85 | ->info('The path to the directory where Vue controller components are stored - relevant only when using symfony/asset-mapper.') 86 | ->defaultValue('%kernel.project_dir%/assets/vue/controllers') 87 | ->end() 88 | ->arrayNode('name_glob') 89 | ->info('The glob patterns to use to find Vue controller components inside of controllers_path') 90 | // find .js (.vue SFC are not supported) - they must be written or compiled to .js 91 | ->defaultValue(['*.js']) 92 | ->scalarPrototype()->end() 93 | ->end() 94 | ->end(); 95 | 96 | return $treeBuilder; 97 | } 98 | 99 | private function isAssetMapperAvailable(ContainerBuilder $container): bool 100 | { 101 | if (!interface_exists(AssetMapperInterface::class)) { 102 | return false; 103 | } 104 | 105 | // check that FrameworkBundle 6.3 or higher is installed 106 | $bundlesMetadata = $container->getParameter('kernel.bundles_metadata'); 107 | if (!isset($bundlesMetadata['FrameworkBundle'])) { 108 | return false; 109 | } 110 | 111 | return is_file($bundlesMetadata['FrameworkBundle']['path'].'/Resources/config/asset_mapper.php'); 112 | } 113 | } 114 | --------------------------------------------------------------------------------