For years, Webpack Encore was the answer to asset management in Symfony, and it still works. But the JavaScript bundler landscape has moved on since Encore was designed. Webpack needed a loader wired up for nearly everything (Sass, TypeScript, you name it), which is exactly why Encore existed: to hide that configuration behind a friendly API. Over the last few years a new generation emerged, Vite, esbuild, Rsbuild (on Rspack), Rolldown, built on native-speed cores and far simpler than Webpack: TypeScript, JSX and Sass work with little to no configuration, and a dev server starts in a blink.
At the same time, Symfony AssetMapper arrived for developers who don't want a build step at all, and it has become the default choice for a large share of Symfony apps. That's great for the no-build camp, but it left an open question for everyone else: developers who genuinely need a bundler, whether for TypeScript, JSX, Vue, Svelte, or just a front-end large enough to benefit from proper tooling. What comes after Encore for them?
Symfony Reprise is an answer to that question. It's a Symfony bundle plus an npm plugin that brings Encore's Symfony integration to modern bundlers.
What is Symfony Reprise?
Reprise ships in two halves that work together:
- A Composer bundle,
symfony/repriseon Packagist (the PHP side,RepriseBundle), installed the usual Symfony way. - An npm package,
@symfony/repriseon npm (the JavaScript side). It is built on unplugin, a library that provides one unified plugin API across bundlers, and that is precisely what lets a single Reprise codebase run on both Vite and Rsbuild.
The core idea, and the reason the project exists, is deliberately narrow. Modern bundlers already handle Sass, Less, PostCSS, TypeScript, JSX, Vue, Svelte, code splitting, content hashing, source maps, minification and HMR on their own, and they do it well. Reprise does not reimplement any of that. It provides only the Symfony-side glue those bundlers leave out, which happens to be exactly what made Encore valuable in the first place: generating the Encore-compatible entrypoints.json and manifest.json files, and rendering the corresponding <script>/<link> tags in Twig.
Concretely, that glue covers multiple entry points, entrypoints.json generation in both build and dev modes, manifest.json with content-hash asset versioning, copying static files like images and fonts into the build output, wiring Twig to the running dev server for HMR, the Twig tag rendering itself, Symfony UX / Stimulus controller integration, CDN support, and Subresource Integrity (SRI) hashes.
Installation is a two-step affair, Composer for the bundle and npm for the plugin:
1 2
$ composer require symfony/reprise
$ npm install @symfony/reprise --save-dev
From there, configuration depends on which bundler you picked. With Vite:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// vite.config.ts
import { defineConfig } from 'vite'
import Symfony from '@symfony/reprise/vite'
export default defineConfig({
build: {
rolldownOptions: {
input: {
// TypeScript, Sass and JSX just work, no loader config needed
app: './assets/app.ts',
},
},
},
plugins: [
Symfony({
// options
}),
],
})
With Rsbuild:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core'
import Symfony from '@symfony/reprise/rsbuild'
export default defineConfig({
source: {
entry: {
// TypeScript, Sass and JSX just work, no loader config needed
app: './assets/app.ts',
},
},
plugins: [
Symfony({
// options
}),
],
})
Once entrypoints.json exists, rendering an entry point in Twig is one call per tag type:
1 2
{{ reprise_entry_link_tags('app') }}
{{ reprise_entry_script_tags('app') }}
This mirrors Encore's encore_entry_link_tags and encore_entry_script_tags exactly, so for Encore users it's a tag-for-tag swap.
Those same tags do double duty across modes. Start the dev server (vite or rsbuild dev) and Reprise repoints entrypoints.json at it and injects the HMR client, so reprise_entry_script_tags loads your modules straight from the running server with hot module replacement: save a file and the browser updates in place, no rebuild, no full reload. In a production build the same tags resolve to the hashed files on disk instead. The dev server is native to the bundler, so it's instant to start and there's nothing to run alongside Symfony beyond it; Reprise just points Symfony at it, in place of Encore's webpack-dev-server glue.
Now, the Node.js question, because it comes up immediately: yes, using Reprise means running Node and installing npm packages. That's the point, not a drawback. Modern bundlers are Node tools, and Reprise embraces the JavaScript ecosystem instead of hiding it behind a PHP-shaped wrapper. If you used Encore, you already have this workflow set up; nothing changes there. If avoiding Node entirely is the goal, that's exactly what AssetMapper is for, and it's covered below.
What Symfony Reprise is not
Worth being precise about the boundaries here.
It's not a bundler. The modern bundlers do the actual work; Reprise is the thin Symfony integration layer sitting on top of them.
It's not a config wrapper the way Encore was. Encore wrapped Webpack's loaders behind its own API, things like enableSassLoader() and a dozen siblings, because Webpack needed that wiring. Modern bundlers don't: Reprise doesn't re-expose the bundler's configuration, and you configure Sass, TypeScript, and everything else the native way, out of the box, because they already handle it well. Reprise's job stops at the Symfony glue.
It's not a replacement for AssetMapper, and it isn't competing with it. AssetMapper is the right default when you want zero build step and no Node.js at all, and it stays the recommendation for a lot of apps. But staying off Node means staying off the JavaScript ecosystem a real front-end leans on: no test runners, whether unit (Vitest, Jest) or end-to-end (Playwright, Cypress), and no Vue or React without extra tooling, since component frameworks assume a bundler. To get TypeScript, Sass, or Tailwind on AssetMapper, you assemble a stack of single-purpose Symfony bundles, one per concern, and the list keeps growing: even linting and formatting now have their own, like BiomeJsBundle and OxcBundle for Biome and Oxc.
Each of those bundles re-wraps a JavaScript tool behind a PHP package, effectively rebuilding the JavaScript ecosystem one bundle at a time, echoing the same pattern discussed below for Encore. That works, and stays pleasant, while the front-end is small to medium. Past that point, a modern bundler gives you all of it natively (test runner, component framework, formatter, TypeScript), and Reprise is for when you genuinely need that: a sizeable front-end or JS tooling AssetMapper isn't meant to cover. Two tools, two different needs, no overlap to argue about.
It's not an automatic Encore-to-Reprise migration tool either. The Twig API mirrors Encore closely enough that your templates barely change, but the build configuration itself has to be rewritten, from webpack.config.js to vite.config.ts or rsbuild.config.ts. That part is manual.
What about Webpack Encore?
Encore is not dead, and it isn't going unmaintained: it still receives bug fixes and dependency and peer-dependency updates. What it has stopped getting is new feature work. In practice it's in maintenance mode, not active development.
The reasoning behind that was discussed openly in the RFC "The future of Webpack Encore". Encore is built on Webpack, and Webpack has aged relative to the current generation of bundlers built around esbuild, SWC, Rolldown, Rspack/Rsbuild and Vite. Rather than keep reinventing JavaScript tooling inside PHP land, the direction that came out of that discussion was to lean on the mature modern bundlers and provide a thin, official Symfony integration on top of them. Reprise is that integration.
If you're on Encore today, the path forward is not a rewrite. The entrypoints.json/manifest.json contract is the same, and the Twig tags are a near tag-for-tag swap, so migrating is mostly about swapping the build tool underneath, not rewriting your templates or your backend code. As of 0.6, Reprise has reached near feature-parity with Encore and ships a "Migrating from Webpack Encore" guide that walks the whole path: it maps every Encore.* call to its Vite, Rsbuild, or Reprise equivalent, and a good number of them simply disappear because the bundler already handles natively what Encore had to configure explicitly. The bundle config carries over almost key-for-key too; for example, webpack_encore.output_path becomes reprise.output_path.
What's next?
Some of the groundwork is already done. A Symfony Flex recipe for Reprise has landed alongside the StimulusBundle recipe updated to version 3.3, both merged in symfony/recipes. Reprise now has official documentation on symfony.com, and EasyAdminBundle supports Reprise assets alongside its existing Encore support.
The Symfony UX story is growing beyond Stimulus too. Reprise 0.4 added React Fast Refresh under Vite, and with UX React 3.4 and UX Vue 3.4 you can use Symfony UX React and Vue components in a Reprise app, registered the same way as your Stimulus controllers. The Flex recipes that wire this up for UX React and UX Vue are still in review, so expect some manual setup for now.
I want to be candid about where the project stands: Reprise is experimental and still 0.x. Expect changes, some of them possibly drastic, as the design settles. The bundlers underneath are stable and mature; it's the Symfony glue that's young. That's exactly why early adopters and feedback matter right now, while the API can still move.
If you're curious, try Reprise on a real project, read through the code, open issues, or contribute directly on GitHub. This is the stage where the community has the most influence over what comes after Encore.
Encore isn't going away tomorrow. But the road ahead for Symfony front-ends that need a real build step runs through modern bundlers, and Reprise is how Symfony meets them there. Feedback, bug reports, and pull requests are all welcome.
Nice!!!!
Nice!
I think a Vite update just dropped that moves the
inputto the toplevel, so no need forrolldownOptionsthere anymore!I've been missing this, and looking at other Vite bundles there was always a little thing missing (often lazy loading Stimulus controllers).
@Joris do you have any hints about the configuration change? I don’t see it on Vite website.
Thanks!