如何为EEx.function_from_file指定目标模块名称
解决EEx模板子目录函数名冲突并生成子模块函数的方案
要实现子目录模板编译到对应子模块的需求,不能简单给函数名加前缀,而是需要根据目录结构动态创建子模块,再在子模块内编译模板函数。以下是具体实现步骤和代码:
核心思路
- 遍历所有模板文件(包括子目录),解析每个文件的相对路径
- 根据路径拆分出目录部分,将目录名转换为驼峰式的子模块名
- 根目录模板直接在当前模块生成函数,子目录模板则先创建对应子模块,再在子模块内生成函数
完整代码实现
defmodule MyApp.Templates do defmacro __using__(opts) do template_dir = Keyword.get(opts, :dir, "templates") quote bind_quoted: [template_dir: template_dir] do # 递归获取所有.eex模板文件 template_files = Path.wildcard(Path.join(template_dir, "**/*.eex")) Enum.each(template_files, fn file -> relative_path = Path.relative_to(file, template_dir) # 移除扩展名并拆分路径为列表 path_parts = relative_path |> Path.rootname() |> Path.split() # 提取函数名(路径最后一部分) fun_name = path_parts |> List.last() |> String.to_atom() # 提取目录部分并转换为驼峰式模块名 module_parts = path_parts |> Enum.drop(-1) |> Enum.map(&Macro.camelize/1) if Enum.empty?(module_parts) do # 根目录模板:直接在当前模块定义函数 EEx.function_from_file(:def, fun_name, file, [:assigns]) else # 子目录模板:构建子模块全名 child_module = Module.concat(__MODULE__, module_parts) # 避免重复定义子模块 unless Module.defines?(child_module) do # 动态创建子模块并编译模板函数 Module.create(child_module, quote do EEx.function_from_file(:def, unquote(fun_name), unquote(file), [:assigns]) end, __ENV__) end end end) end end end
代码说明
- 路径解析:通过
Path.relative_to获取文件相对于模板根目录的路径,再用Path.split拆分出目录和文件名 - 模块名转换:使用
Macro.camelize将下划线命名的目录(如sub_domain)转换为符合Elixir规范的驼峰式模块名(如SubDomain) - 子模块创建:通过
Module.concat拼接父模块和子模块后缀,生成完整子模块名;用Module.create动态创建模块,同时用Module.defines?避免重复编译 - 函数生成:根目录模板直接在父模块生成函数,子目录模板在对应子模块内生成函数,彻底解决函数名冲突问题
使用效果
templates/index.eex→ 编译为MyApp.Templates.index/1函数templates/sub_domain/index.eex→ 编译为MyApp.Templates.SubDomain.index/1函数
内容的提问来源于stack exchange,提问作者mszmurlo
相关产品推荐
相关产品推荐

