编译时如何将外部.ex文件内容合并到当前Elixir文件中?
Elixir编译时动态加载外部.ex文件并合并代码
核心思路
利用Elixir的编译时宏读取外部文件,解析其代码AST,提取函数定义并注入到当前模块中,实现编译阶段的代码合并,而非运行时加载。
步骤1:实现编译时宏
创建一个工具模块,定义处理外部文件注入的宏:
defmodule MyApp.CompilerHelpers do defmacro inject_external_file_if_exists(file_path) do # 获取当前调用模块的文件路径,拼接外部文件的完整路径 caller_file = __CALLER__.file base_dir = Path.dirname(caller_file) full_path = Path.join(base_dir, file_path) if File.exists?(full_path) do # 读取文件内容并解析为AST {:ok, content} = File.read(full_path) {ast, _} = Code.string_to_quoted(content) # 提取外部文件中的函数定义(def/defp) functions = case ast do # 如果外部文件包含完整模块定义,提取模块内的函数 {:defmodule, _, [_module, [do: {:__block__, _, body}]]} -> Enum.filter(body, fn {:def, _, _} -> true {:defp, _, _} -> true _ -> false end) # 如果外部文件直接写函数块,提取其中的函数 {:__block__, _, body} -> Enum.filter(body, fn {:def, _, _} -> true {:defp, _, _} -> true _ -> false end) # 单个函数的情况 {:def, _, _} = func -> [func] {:defp, _, _} = func -> [func] _ -> [] end # 将提取的函数注入当前模块 {:__block__, [], functions} else # 文件不存在时返回空代码块,不影响编译 {:__block__, [], []} end end end
步骤2:在目标模块中使用宏
修改你的控制器文件,导入宏并调用:
defmodule MyAppWeb.MyController do use MyAppWeb, :controller import MyApp.CompilerHelpers def page1(conn, params) do # 原有业务逻辑 end # 编译时注入外部文件的函数 inject_external_file_if_exists("my_controller.personal.ex") end
步骤3:编写外部专属代码
外部文件可以选择两种格式:
- 直接写函数(推荐,更简洁):
# my_controller.personal.ex def specific_page1(conn, params) do # 子项目专属逻辑 end def specific_page2(conn, params) do # 子项目专属逻辑 end
- 保留完整模块定义(兼容你原有的写法):
# my_controller.personal.ex defmodule MyAppWeb.MyController do use MyAppWeb, :controller def specific_page1(conn, params) do # 子项目专属逻辑 end def specific_page2(conn, params) do # 子项目专属逻辑 end end
关键说明
- 编译时合并:宏在编译阶段执行,最终生成的模块会包含原有的函数和外部文件的函数,不会出现覆盖问题。
- 路径兼容性:通过
__CALLER__.file获取当前模块的文件路径,确保相对路径始终正确,不受模块所在目录影响。 - 安全过滤:只提取
def/defp函数定义,避免外部文件中的模块属性、use语句等干扰当前模块的编译。 - Git忽略:将
*.personal.ex加入项目的.gitignore文件,确保子项目专属代码不会被提交到模板仓库。
内容的提问来源于stack exchange,提问作者Duminamonri
相关产品推荐
相关产品推荐

