Just to clarify the terms: Mix does not load the files in lib/, it compiles them if needed, and then uses the compiled byte code (without having to execute the whole file again).
You can load any file in a directory by calling Code.require_file/1 or Code.load_file/1 but that does not generate any artifact in disk. If later on you call Code.require_file/1, the same files will be evaluated again, which may take time. The idea of compiling is exactly so you don't pay this price every time.
That said, let's answer your question directly.
A way to load everything in a directory is:
dir
|> Path.join("**/*.exs")
|> Path.wildcard()
|> Enum.map(&Code.require_file/1)
If you want to compile them, use:
dir
|> Path.join("**/*.ex")
|> Path.wildcard()
|> Kernel.ParallelCompiler.files_to_path("path/for/beam/files")
If you want to just compile another directory in the context of a project (with a mix.exs and what not), you can give any directory you want to Mix inside def project:
elixirc_paths: ["lib", "my/other/path"]
That's it.