Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Twig Js Bundle Laravel Package

forknetwork/twig-js-bundle

A Symfony bundle that integrates Twig.js so you can reuse Twig templates in the browser. It provides the wiring and helpers needed to load, compile, and render Twig templates client-side, bridging server-side Twig and front-end JavaScript.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a Symfony2-era package, not a Laravel one. Before you invest time, decide which of the two things you actually want:

  1. The bundle itself — only useful if you're maintaining a legacy Symfony 2/3 app. Drop-in install (composer require forknetwork/twig-js-bundle), register ForkNetwork\TwigJsBundle\TwigJsBundle in AppKernel, then look at:

    • DependencyInjection/Configuration.php — the actual config keys (don't trust the README blindly; it's from 2019 and the config tree is the source of truth).
    • DependencyInjection/TwigJsExtension.php / compiler passes — this is where templates get collected and wired into the asset pipeline. Almost all behaviour you'll want to change lives here.
    • Resources/config/services.yml — how the Twig loader and the twig.js asset are registered.
  2. The idea, ported to Laravel — which is what most readers of this assessment will actually do. Twig.js is just an npm package (twig); the bundle is only glue. Skip the bundle entirely and replicate the glue in your own build.

Minimal Laravel-flavoured setup:

npm install --save-dev twig
// resources/js/twig/compile.js
const Twig = require('twig');
const fs = require('fs');
const path = require('path');

const srcDir = path.resolve(__dirname, '../../../resources/twig');
const outFile = path.resolve(__dirname, '../twig-templates.js');

const templates = {};
for (const file of fs.readdirSync(srcDir)) {
  if (!file.endsWith('.twig')) continue;
  templates[file.replace(/\.twig$/, '')] = fs.readFileSync(path.join(srcDir, file), 'utf8');
}

fs.writeFileSync(outFile, `export default ${JSON.stringify(templates)};\n`);
// resources/js/app.js
import Twig from 'twig';
import templates from './twig-templates';

Object.entries(templates).forEach(([id, data]) => {
  Twig.twig({ id, data, allowInlineIncludes: true, autoescape: true });
});

document.querySelectorAll('[data-twig]').forEach(async (el) => {
  const res = await fetch(`/api/context/${el.dataset.twig}`);
  el.innerHTML = Twig.twig({ ref: el.dataset.twig }).render(await res.json());
});

First use case: one template that must render identically on the server and in the browser — a user card, an invoice line item, a notification row. Compile it once at build time, render it client-side from JSON you already fetch from an API route, and stop maintaining two copies (Blade + JS string concatenation).

If you're starting fresh in Laravel, the honest answer is: this package is not the tool. Use Blade server-side and Alpine/Vue/Livewire client-side, and only reach for twig.js if you have a pile of existing .twig files you need to reuse in the browser without rewriting them.

Implementation Patterns

Build-time precompilation (default, preferred). Compile every template into a single JS module at build time as above, then register by id. Zero runtime HTTP requests, one cacheable asset, and a build step that fails loudly if a template is malformed. Wire it into vite.config.js (build.rollupOptions.input) or webpack.mix.js so npm run build produces it.

Runtime fetch + compile (only for user-authored templates). If templates come from the database (email bodies, CMS snippets), fetch the source and call Twig.twig({ data }) on demand. Cache the compiled object in a Map keyed by a content hash — recompiling per render is measurably slow.

Context parity between Laravel and the browser. Let the server own the data shape:

// app/Http/Controllers/TwigContextController.php
public function show(Notification $notification)
{
    return response()->json([
        'id'    => $notification->id,
        // mirror exactly what the twig template expects
        'name'  => $notification->actor->name,
        'created_at' => $notification->created_at->toIso8601String(),
    ]);
}

Then write a snapshot test in PHPUnit that renders the same .twig file via a thin Twig-PHP shim and compares to the twig.js output (see Gotchas).

Stub the Symfony/Laravel runtime. Twig.js knows nothing about path(), asset(), csrf_token(), trans(). Register them up front:

Twig.extendFunction('asset', (p) => `/assets/${p}`);
Twig.extendFunction('path', (name, params = {}) => route(name, params)); // your JS route helper
Twig.extendFilter('trans', (v) => window.__translations?.[v] ?? v);

Do this in one twig-setup.js imported before anything renders, and keep the list next to the template directory so it's reviewed when templates change.

Namespace your ids. Twig.twig({ ref: 'card' }) is a global registry. Prefix with the domain (user/card, billing/line-item) or you will collide the moment two features both ship a card.twig.

Symfony-bundle usage, if you're stuck on legacy. The bundle typically exposes a config list of template paths and dumps them into a JS asset via Assetic or the asset pipeline. Read Configuration.php for the exact keys, then serve that asset through your normal layout. Do not enable it in dev without inspecting the generated dump — the failure mode is a silently empty asset when the config path is wrong.

Progressive enhancement over full client rendering. Render server-side in Blade for first paint, then use twig.js only to re-render on interaction. You get the shared-template benefit without shipping an empty <div> and a spinner to every user.

Gotchas and Tips

  • The package is effectively unmaintained. Last release 2019-03-14, 3 stars, 0 dependents, Symfony2/3-era internals (AcmeBundle:Controller:action notation, services.yml, likely Assetic). Do not add it to a new project. If you inherit it, pin your PHP/Symfony versions and treat it as frozen code you may have to fork.
  • Twig.js ≠ Twig PHP. The JS port lags. Recent syntax — {% apply %}, arrow functions, named arguments, ?? null-coalescing edge cases — may parse differently or not at all. Before adopting, run your real templates through Twig.twig({data}) in Node and count the failures.
  • Autoescape defaults differ. Pass autoescape: true explicitly on every Twig.twig() call. Getting this wrong is an XSS hole, not a cosmetic bug.
  • Inheritance needs the whole tree registered. {% extends %}/{% include %} resolve by id in the same registry. If a parent template isn't registered under the exact id used (case-sensitive), you get a runtime "template not found" — often only on the one code path that hits that child.
  • Date and number formatting drift. |date('Y-m-d') behaves differently against a JS Date than a PHP string; |number_format locale handling is inconsistent. Standardise on ISO-8601 strings from the API and avoid locale-sensitive filters in shared templates.
  • Debugging is poor. Errors usually throw with a template id but sometimes without a line number. Bisect by rendering with Twig.cache(false) and feeding the smallest possible context. A compile step that runs in CI catches most of this before the browser does.
  • Cache the compiled bundle aggressively. Filename-hash it (Vite/Mix do this for free) and set long-lived Cache-Control. The compiled output only changes on deploy.
  • Test for drift. One PHPUnit test that renders each shared template server-side and asserts against a checked-in fixture is the cheapest insurance against the Laravel and JS renderers silently diverging six months from now.
  • Extension points, if you do keep the bundle: the compiler pass is where to add template paths; the service definition is where to swap the asset dumper. Don't patch vendor/ — fork and use a VCS repository entry in composer.json, because upstream isn't taking PRs.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings