diff --git a/composer.json b/composer.json
index 3866b1bda..df62c0952 100644
--- a/composer.json
+++ b/composer.json
@@ -50,7 +50,8 @@
"rector/rector": "^2.0-rc2",
"spatie/phpunit-snapshot-assertions": "^5.1.6",
"spaze/phpstan-disallowed-calls": "^4.0",
- "symplify/monorepo-builder": "^11.2"
+ "symplify/monorepo-builder": "^11.2",
+ "twig/twig": "^3.16"
},
"replace": {
"tempest/auth": "self.version",
diff --git a/src/Tempest/View/src/Renderers/TwigConfig.php b/src/Tempest/View/src/Renderers/TwigConfig.php
new file mode 100644
index 000000000..af3584de6
--- /dev/null
+++ b/src/Tempest/View/src/Renderers/TwigConfig.php
@@ -0,0 +1,36 @@
+ $this->debug,
+ 'charset' => $this->charset,
+ 'strict_variables' => $this->strictVariables,
+ 'autoescape' => $this->autoescape,
+ 'cache' => $this->cachePath ?: false,
+ 'auto_reload' => $this->autoReload,
+ 'optimizations' => $this->optimizations,
+ ];
+ }
+}
diff --git a/src/Tempest/View/src/Renderers/TwigInitializer.php b/src/Tempest/View/src/Renderers/TwigInitializer.php
new file mode 100644
index 000000000..51b7e4a85
--- /dev/null
+++ b/src/Tempest/View/src/Renderers/TwigInitializer.php
@@ -0,0 +1,33 @@
+getName() === Environment::class;
+ }
+
+ #[Singleton]
+ public function initialize(ClassReflector $class, Container $container): object
+ {
+ $twigConfig = $container->get(TwigConfig::class);
+ $twigLoader = new FilesystemLoader($twigConfig->viewPaths);
+
+ return new Environment($twigLoader, $twigConfig->toArray());
+ }
+}
diff --git a/src/Tempest/View/src/Renderers/TwigViewRenderer.php b/src/Tempest/View/src/Renderers/TwigViewRenderer.php
new file mode 100644
index 000000000..174e2a72b
--- /dev/null
+++ b/src/Tempest/View/src/Renderers/TwigViewRenderer.php
@@ -0,0 +1,25 @@
+twig->render($view->getPath(), $view->getData()));
+ }
+}
diff --git a/tests/Integration/View/TwigViewRendererTest.php b/tests/Integration/View/TwigViewRendererTest.php
new file mode 100644
index 000000000..ceee66a88
--- /dev/null
+++ b/tests/Integration/View/TwigViewRendererTest.php
@@ -0,0 +1,41 @@
+container->get(ViewConfig::class);
+
+ $viewConfig->rendererClass = TwigViewRenderer::class;
+
+ $this->container->config(new TwigConfig(
+ viewPaths: [__DIR__ . '/twig'],
+ cachePath: __DIR__ . '/../../../.cache/tempest/twig/cache',
+ ));
+
+ $renderer = $this->container->get(ViewRenderer::class);
+
+ $html = $renderer->render(view('index.twig', ...['foo' => 'bar']));
+
+ $this->assertStringEqualsStringIgnoringLineEndings(<<
+ bar
+
+ HTML
+ , $html);
+ }
+}
diff --git a/tests/Integration/View/twig/base.twig b/tests/Integration/View/twig/base.twig
new file mode 100644
index 000000000..d78025dd0
--- /dev/null
+++ b/tests/Integration/View/twig/base.twig
@@ -0,0 +1,3 @@
+
+{% block content %}{% endblock %}
+
diff --git a/tests/Integration/View/twig/index.twig b/tests/Integration/View/twig/index.twig
new file mode 100644
index 000000000..2ecf303ae
--- /dev/null
+++ b/tests/Integration/View/twig/index.twig
@@ -0,0 +1,5 @@
+{% extends "base.twig" %}
+
+{% block content %}
+{{ foo }}
+{% endblock %}