PHPackages                             mediagone/vue-in-twig-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. mediagone/vue-in-twig-bundle

ActiveSymfony-bundle[Utility &amp; Helpers](/categories/utility)

mediagone/vue-in-twig-bundle
============================

Vue.js integration for Twig/Symfony without a Node.js toolchain

0.2.0(today)00MITJavaScriptPHP &gt;=8.1

Since Jun 9Pushed todayCompare

[ Source](https://github.com/Mediagone/vue-in-twig-bundle)[ Packagist](https://packagist.org/packages/mediagone/vue-in-twig-bundle)[ RSS](/packages/mediagone-vue-in-twig-bundle/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (2)Versions (3)Used By (0)

mediagone/vue-in-twig-bundle
============================

[](#mediagonevue-in-twig-bundle)

A standalone Vue.js 3 integration for Twig/Symfony — without a Node.js toolchain.

The pattern
-----------

[](#the-pattern)

This bundle formalizes a specific integration approach: **Vue components written as x-templates, rendered and composed server-side by Twig**.

```
PHP (enums, config, IDs) → Twig props → Vue component (presentation)
                                               ↕
                                     XHR → internal API (business data)

```

What Twig brings that Vue alone cannot do:

- Inject PHP constants without an API: `v-if="type === '{{ constant('Domain\\Block::TYPE_A') }}'"`
- Type-safe Symfony URLs with dynamic Vue expressions via `vue_path()`
- Server-side component composition (Twig blocks/embeds)
- Initial data without an API call: `:account="{{ account|vue_props_encode }}"`

No bundler, no build step, no `node_modules`.

---

Installation
------------

[](#installation)

```
composer require mediagone/vue-in-twig-bundle
```

Register the bundle in `config/bundles.php`:

```
Mediagone\VueInTwigBundle\VueInTwigBundle::class => ['all' => true],
```

The Twig namespace `@VueInTwig/` is configured automatically.

Load **Vue 3 (full build, with compiler)** in your layout — the compiler is required since templates are compiled at runtime:

```

```

---

Usage
-----

[](#usage)

### `{% vue_app 'selector' %}...{% endvue_app %}`

[](#-vue_app-selector--endvue_app-)

Wraps all Vue initialization. Place it in your base layout.

```
{% vue_app '#App' %}
    {% block BODY_CONTENT %}{% endblock %}
{% endvue_app %}
```

What it outputs:

1. **Opening tag** → `window.VUE_APP = Vue.createApp({});` + `setup.js` (delimiters, global mixin)
2. **Body** → rendered normally; `vue_use()` calls queue components silently (zero output)
3. **Closing tag** → all queued component templates + scripts (deduplicated, in call order) + `VUE_APP.mount('selector');`

---

### `{{ vue_use('Category/ComponentName') }}`

[](#-vue_usecategorycomponentname-)

Declares a Vue component dependency. Can be called from any partial, in any order, before `{% endvue_app %}`. Duplicate calls are ignored (include-once).

```
{# In a partial — declares its Vue dependency #}
{{ vue_use('Controls/DatePicker') }}

{# Called twice → included once #}
{{ vue_use('Layout/Modal') }}
{{ vue_use('Layout/Modal') }}  {# ignored #}
```

Each call queues two files from the bundle's `templates/` directory:

- `Category/ComponentName.vue.twig` — the x-template (if it exists)
- `Category/ComponentName.vue.js` — the component registration (if it exists)

---

### `|vue_props_encode`

[](#vue_props_encode)

HTML-safe replacement for `|json_encode`. Prevents XSS when injecting PHP data as Vue props.

```
{# Before — XSS risk #}
:account="{{ account|json_encode }}"

{# After #}
:account="{{ account|vue_props_encode }}"
```

Applies `JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT`.

---

### `vue_path(route, staticParams, dynamicParams)`

[](#vue_pathroute-staticparams-dynamicparams)

Generates a Symfony `path()` with dynamic Vue expressions, via two separate parameter arrays — no reserved characters, no fragile string conventions.

```
{# Before — verbose and fragile #}
:url="'{{ path('ajax_account', {accountId: '__ID__', fields: 'full'}) }}'.replace('__ID__', account.id)"

{# After #}
:url="{{ vue_path('ajax_account', {fields: 'full'}, {accountId: 'account.id'}) }}"
```

Generates: `'/ajax/account?fields=full&accountId=__ACCOUNTID__'.replace('__ACCOUNTID__', account.id)`

---

Twig/Vue writing conventions
----------------------------

[](#twigvue-writing-conventions)

Two template engines coexist in the same HTML, each running at a different time:

EngineRunsSyntaxServerTwigAt request time (PHP)`{{ }}`ClientVueIn the browser (JS)`[[ ]]`### Delimiters: `[[ ]]` instead of `{{ }}`

[](#delimiters---instead-of--)

Vue's default `{{ }}` delimiters conflict with Twig. `setup.js` reconfigures them to `[[ ]]`. Use `[[ ]]` everywhere Vue reactivity is needed — in x-templates (`.vue.twig`) and in the mounted HTML.

```
{# Vue reactive expression — evaluated in the browser #}
[[ item.title ]]
[[ count ]] items
```

### Injecting server-side data into Vue props

[](#injecting-server-side-data-into-vue-props)

Twig `{{ }}` still works inside HTML attributes — Twig renders the attribute value as a string, Vue reads it as a JS expression. This is how PHP data crosses the server/client boundary.

```
{# Twig renders the JSON string, Vue receives it as a prop #}
:account="{{ account|vue_props_encode }}"

{# Static PHP value, no reactivity needed #}
:locale="'{{ app.request.locale }}'"
:max-size="{{ maxFileSizeBytes }}"
```

### Injecting PHP constants into Vue expressions

[](#injecting-php-constants-into-vue-expressions)

Constants and enums can be injected directly into Vue attribute values — Twig renders them as literal strings before Vue compiles the template.

```
v-if="block.type === '{{ constant('App\\Domain\\Block::TYPE_VIDEO') }}'"
:allowed-types="['{{ constant('App\\Domain\\Media::TYPE_IMAGE') }}', '{{ constant('App\\Domain\\Media::TYPE_PDF') }}']"
```

### Summary

[](#summary)

```
{# ✓ Twig: server-side value injected as prop #}
:initial-count="{{ items|length }}"

{# ✓ Vue: reactive expression in the browser #}
[[ count ]]

{# ✓ Both: Twig renders the URL string, Vue evaluates it as JS #}
:url="{{ vue_path('api_item', {}, {id: 'item.id'}) }}"

{# ✗ Wrong: Twig delimiter inside x-template — use [[ ]] #}
{{ message }}
```

---

Components
----------

[](#components)

### File naming convention

[](#file-naming-convention)

`.vue.twig` + `.vue.js` — immediately identifies Vue files among other Twig templates.

### Available components

[](#available-components)

#### `Behaviors/` — renderless components (no wrapper element, modify the child directly)

[](#behaviors--renderless-components-no-wrapper-element-modify-the-child-directly)

ComponentDescription`AutoResize`Dynamically resizes a textarea/input to fit its content`Draggable`Native HTML5 drag &amp; drop. Reorders a list's children and moves items between lists sharing a `group`. Insertion-line or gap-placeholder feedback, keeps empty lists droppable. Props `v-model`/`group`/`sort`/`empty-height`/`use-placeholder`, themable via `--vue-draggable-indicator-*` CSS vars, emits `change`. Zero dependency.#### `Layout/` — structural containers

[](#layout--structural-containers)

ComponentDescription`Modal`Modal dialog with slot-based content`LockWrapper`Locks interaction on its content (loading state)#### `Controls/` — interaction primitives

[](#controls--interaction-primitives)

ComponentDescription`DatePicker`Date selection input`DatetimePicker`Date + time selection input`DropZone`File selection + validation + preview → emits `select` with files. Parent handles upload.`UploadZone`File selection + integrated upload (axios) + optional crop → emits `uploaded` with server response.`ImageCropper`Image crop UI`SwitchButton`Toggle switch`ToggleButton`Button that toggles between two states#### `Widgets/` — autonomous composite components

[](#widgets--autonomous-composite-components)

ComponentDescription`DataEditor`Formalizes the editor pattern (`referenceData` + `checkForSave`) as a base component with slots`DataList`Formalizes the list pattern (CRUD + search + debounce) with a slot architecture`NotificationBar`Displays Symfony flash messages passed as a prop---

`setup.js` — runtime configuration
----------------------------------

[](#setupjs--runtime-configuration)

Included automatically by `{% vue_app %}`. Configures:

- `VUE_APP.config.compilerOptions.delimiters = ['[[', ']]']` — avoids conflicts with Twig's `{{ }}`
- Global mixin: `format_date()`, `slugify()`
- `window.VUE_CONFIG` defaults (e.g. `debounceSearch: 300`)

Override after the opening tag if needed:

```
{% vue_app '#App' %}
    VUE_CONFIG.debounceSearch = {{ debounce_ms }};
    {% block BODY_CONTENT %}{% endblock %}
{% endvue_app %}
```

---

Local development
-----------------

[](#local-development)

To use the bundle from a local path instead of Packagist, add a path repository in the consuming project's `composer.json`:

```
{
    "repositories": [
        { "type": "path", "url": "/absolute/path/to/vue-in-twig-bundle" }
    ],
    "require": {
        "mediagone/vue-in-twig-bundle": "*"
    }
}
```

Composer will symlink (or junction on Windows) the directory into `vendor/`. Changes to the library are immediately reflected.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity33

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/32240357?v=4)[Bruce](/maintainers/Mediagone)[@Mediagone](https://github.com/Mediagone)

---

Top Contributors

[![Mediagone](https://avatars.githubusercontent.com/u/32240357?v=4)](https://github.com/Mediagone "Mediagone (5 commits)")

---

Tags

frontendno-buildphpsymfonytwigvuevue3vuejs

### Embed Badge

![Health badge](/badges/mediagone-vue-in-twig-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/mediagone-vue-in-twig-bundle/health.svg)](https://phpackages.com/packages/mediagone-vue-in-twig-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.5M370](/packages/easycorp-easyadmin-bundle)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1155.2k](/packages/rcsofttech-audit-trail-bundle)[netgen/content-browser

Netgen Content Browser is a Symfony bundle that provides an interface which selects items from any kind of backend and returns the IDs of selected items back to the calling code.

14114.1k13](/packages/netgen-content-browser)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
