Repository: phoenixframework/phoenix
Pull request #3243 (2019-01-13 17:21:42)

Custom engines per template module

amarraja: @josevalim some of the context got lost when moving this to a PR so will comment here. 

Re: removing the `engine/0` and corresponding `defoverridable` statements. I put these in so this test and the similar one I added would pass:

```elixir
test "engines/0" do
  assert is_map Template.engines
end
```

If I remove them, `MyViewEngine.engines()` would just return the default + global engines no?




Patch:
diff --git a/lib/phoenix/template.ex b/lib/phoenix/template.ex
index f3e16bb30..b18ac001d 100644
--- a/lib/phoenix/template.ex
+++ b/lib/phoenix/template.ex
@@ -37,6 +37,8 @@ defmodule Phoenix.Template do
     * `:root` - the root template path to find templates
     * `:pattern` - the wildcard pattern to apply to the root
       when finding templates. Default `"*"`
+    * `:template_engines` - a map of template engines extensions
+      to template engine handlers
 
   ## Terminology
 
@@ -130,7 +132,7 @@ defmodule Phoenix.Template do
       root = Keyword.fetch!(options, :root)
       @phoenix_root Path.relative_to_cwd(root)
       @phoenix_pattern Keyword.get(options, :pattern, unquote(@default_pattern))
-      @phoenix_template_engines Keyword.get(options, :template_engines, %{})
+      @phoenix_template_engines Enum.into(Keyword.get(options, :template_engines, %{}), Template.engines())
       @before_compile unquote(__MODULE__)
 
       @doc """
@@ -152,11 +154,11 @@ defmodule Phoenix.Template do
     root    = Module.get_attribute(env.module, :phoenix_root)
     pattern = Module.get_attribute(env.module, :phoenix_pattern)
     engines = Module.get_attribute(env.module, :phoenix_template_engines)
-    engines = Enum.into(engines, engines())
 
-    pairs = for path <- find_all(root, pattern) do
-      compile(path, root, engines)
-    end
+    pairs =
+      for path <- find_all(root, pattern, engines) do
+        compile(path, root, engines)
+      end
 
     names = Enum.map(pairs, &elem(&1, 0))
     codes = Enum.map(pairs, &elem(&1, 1))
@@ -188,7 +190,7 @@ defmodule Phoenix.Template do
       Returns true whenever the list of templates changes in the filesystem.
       """
       def __phoenix_recompile__? do
-        unquote(hash(root, pattern)) != Template.hash(@phoenix_root, @phoenix_pattern)
+        unquote(hash(root, pattern, engines)) != Template.hash(@phoenix_root, @phoenix_pattern, @phoenix_template_engines)
       end
     end
   end
@@ -306,9 +308,9 @@ defmodule Phoenix.Template do
   @doc """
   Returns all template paths in a given template root.
   """
-  @spec find_all(root, pattern :: String.t) :: [path]
-  def find_all(root, pattern \\ @default_pattern) do
-    extensions = engines() |> Map.keys() |> Enum.join(",")
+  @spec find_all(root, pattern :: String.t(), %{atom => module}) :: [path]
+  def find_all(root, pattern \\ @default_pattern, engines \\ engines()) do
+    extensions = engines |> Map.keys() |> Enum.join(",")
 
     root
     |> Path.join(pattern <> ".{#{extensions}}")
@@ -320,9 +322,9 @@ defmodule Phoenix.Template do
 
   Used by Phoenix to check if a given root path requires recompilation.
   """
-  @spec hash(root, pattern :: String.t) :: binary
-  def hash(root, pattern \\ @default_pattern) do
-    find_all(root, pattern)
+  @spec hash(root, pattern :: String.t, %{atom => module}) :: binary
+  def hash(root, pattern \\ @default_pattern, engines \\ engines()) do
+    find_all(root, pattern, engines)
     |> Enum.sort()
     |> :erlang.md5()
   end


Test patch:
diff --git a/test/fixtures/templates/custom.foo b/test/fixtures/templates/custom.foo
new file mode 100644
index 000000000..008cbc726
--- /dev/null
+++ b/test/fixtures/templates/custom.foo
@@ -0,0 +1,1 @@
+from foo
\ No newline at end of file
diff --git a/test/phoenix/template_test.exs b/test/phoenix/template_test.exs
index 7d8ca8d27..39ea44ed7 100644
--- a/test/phoenix/template_test.exs
+++ b/test/phoenix/template_test.exs
@@ -121,4 +121,20 @@ defmodule Phoenix.TemplateTest do
   test "generates __phoenix_recompile__? function" do
     refute View.__phoenix_recompile__?
   end
+
+  defmodule CustomEngineView do
+    use Phoenix.Template,
+      root: Path.join(__DIR__, "../fixtures/templates"),
+      template_engines: %{
+        foo: Phoenix.Template.EExEngine
+      }
+
+    def render(template, assigns) do
+      render_template(template, assigns)
+    end
+  end
+
+  test "custom view renders custom templates" do
+    assert CustomEngineView.render("custom", %{}) == "from foo"
+  end
 end