View Source Uniform.Blueprint behaviour (Uniform v0.6.0)
Defines the ejection blueprint for your project.
When used, the blueprint accepts the :templates option, which is not
required. For example, the blueprint:
defmodule Blueprint do
use Uniform.Blueprint, templates: "priv/uniform-templates"
endWould search for templates in the priv/uniform-templates directory. See
template/2 for more information.
the-base_files-section
The base_files Section
The base_files section specifies files that should be ejected which aren't
in lib/my_app. (When running mix uniform.eject my_app.)
defmodule Blueprint do
use Uniform.Blueprint
base_files do
file "my_main_app/application.ex"
cp_r "assets"
# ...
end
endSee base_files/1 for more information.
files-that-are-always-ejected
Files that are always ejected
There are a handful of files that are automatically ejected. You do not need
to specify these in the base_files section.
mix.exs
mix.lock
.gitignore
.formatter.exs
test/test_helper.exs
the-deps-section
The deps Section
Besides the base_files section, the blueprint can also contain a deps
section to configure dependencies.
defmodule Blueprint do
use Uniform.Blueprint
deps do
always do
mix :phoenix
lib :my_component_library
end
mix :absinthe do
mix_deps [:absinthe_plug, :dataloader]
end
end
endSee deps/1 for more information.
modifying-files-programmatically-with-modify
Modifying files programmatically with modify
Lastly, modify can be used whenever you want to transform file contents
during ejection. You can specify a specific filepath or use a regex to match
multiple files.
defmodule Blueprint do
use Uniform.Blueprint
modify "assets/js/app.js", fn file, app ->
String.replace(file, "SOME_VALUE_PER_APP", app.extra[:some_value])
end
modify ~r/_worker.ex/, &MyApp.Modify.modify_workers/1See modify/2 for more information.
preserving-files
Preserving files
Before mix uniform.eject copies any files, the contents in the destination
directory are deleted – except for the .git, deps, and _build
directories.
If there are any other files or directories in the project's root folder
that you would like to preserve (by not deleting them), specify them with
@preserve.
# .env will not be deleted immediately before ejection
@preserve [
".env"
]
full-example
Full Example
Below is an example Blueprint module that shows off a majority of the
features that can be used.
defmodule MyApp.Uniform.Blueprint do
use Uniform.Blueprint, templates: "lib/uniform/templates"
# do not delete this root file when clearing the destination
@preserve [".env"]
@impl Uniform.Blueprint
def extra(app) do
theme =
case app.name.underscore do
"empire_" <> _ -> :empire
"rebel_" <> _ -> :rebel
end
# set `app.extra[:theme]`
[theme: theme]
end
@impl Uniform.Blueprint
def target_path(path, app) do
if is_web_file?(path) do
# modify the path to put it in `lib/some_app_web`
String.replace(path, "lib/#{app.name.underscore}/", "lib/#{app.name.underscore}_web/")
else
path
end
end
# files to eject in every app, which are outside `lib/that_app`
base_files do
# copy these directories wholesale; do NOT run them through code modifiers
cp_r "assets"
# eject these files which aren't in lib/the_app_directory
file ".credo.exs"
file ".github/workflows/elixir.yml"
file "priv/static/#{app.extra[:theme]}-favicon.ico"
file "lib/my_app_web.ex"
# eject a file from an EEx template at "lib/uniform/templates/config/runtime.exs.eex"
# configure the templates directory on line 2
template "config/runtime.exs"
# conditionally eject some files
if deploys_to_aws?(app) do
file "file/required/by/aws"
template "dynamic/file/required/by/aws"
end
if depends_on?(app, :lib, :some_lib) do
template "dynamic/file/required/by/some_lib"
end
end
# run the file contents through this modifier if the file is ejected
modify "lib/my_app_web/templates/layout/root.html.heex", file, app do
file
|> String.replace("empire-favicon.ico", "#{app.extra[:theme]}-favicon.ico")
|> String.replace("empire-apple-touch-icon.png", "#{app.extra[:theme]}-apple-touch-icon.png")
end
# configure dependencies from mix.exs and `lib/`
deps do
# always eject the dependencies in the `always` section;
# don't require adding them to uniform.exs
always do
lib :my_app do
# only eject the following files in `lib/my_app`
only ["lib/my_app/application.ex"]
end
lib :my_app_web do
# only eject the following files in `lib/my_app_web`
only [
"lib/my_app_web/endpoint.ex",
"lib/my_app_web/router.ex",
"lib/my_app_web/channels/user_socket.ex",
"lib/my_app_web/views/error_view.ex",
"lib/my_app_web/templates/layout/root.html.heex",
"lib/my_app_web/templates/layout/app.html.heex",
"lib/my_app_web/templates/layout/live.html.heex"
]
end
# always include these mix dependencies
mix :credo
mix :ex_doc
mix :phoenix
mix :phoenix_html
end
# if absinthe is included, also include absinthe_plug and dataloader
mix :absinthe do
mix_deps [:absinthe_plug, :dataloader]
end
lib :my_data_lib do
# if my_data_lib is included, also include other_lib, faker, and norm
lib_deps [:other_lib]
mix_deps [:faker, :norm]
# if my_data_lib is included, also eject these files
file "priv/my_data_repo/**/*.exs", match_dot: true
file "test/support/fixtures/my_data_lib/**/*.ex"
end
end
end
Link to this section Summary
Callbacks
This callback works like the except/1 instruction for Lib Dependencies,
except that it applies to the lib folder of the ejected app itself.
A callback to add data to app.extra.
Use this optional callback to change the path of files in the ejected codebase.
Functions
Inside of a deps do block, any Mix or Lib dependencies that should be
included in every ejected app should be wrapped in an always do block
The base_files section is where you specify files outside of an ejected
app's lib/my_app and test/my_app directories which should always be
ejected.
cp works exactly like file/2, except that no Code
Transformations are applied to the file.
cp_r works like cp/2, but for a directory instead of a file. The
directory is copied as-is with File.cp_r!/3.
Uniform automatically catalogs all Mix deps by looking into mix.exs to
discover all Mix deps. It also catalogs all Lib deps by scanning the lib/
directory.
In the deps section of your Blueprint, you can specify that a Lib
Dependency excludes certain files.
In base_files and lib blocks, file is used to specify files that are
not in a lib/ directory which should be ejected in the app or along with
the lib.
Lib Dependencies are shared code
libraries in the lib directory of your project. Uniform scans lib/, so
you don't need to inform it about them.
Specify transitive Lib Dependencies of other Lib Dependencies.
Uniform automatically catalogues the deps in mix.exs, so there are only two
scenarios where you need to list them in your Blueprint.
Specify transitive Mix Dependencies of other Lib/Mix Dependencies.
Modify the contents of one or more files during ejection.
In the deps section of your Blueprint, you can specify that a Lib
Dependency only includes certain files.
In base_files and lib blocks, template is used to specify EEx templates
that should be rendered and then ejected.
Link to this section Callbacks
@callback app_lib_except(app :: Uniform.App.t()) :: [Path.t() | Regex.t()]
This callback works like the except/1 instruction for Lib Dependencies,
except that it applies to the lib folder of the ejected app itself.
When running mix uniform.eject my_app, any files in lib/my_app or
test/my_app which match the paths or regexes returned by app_lib_except
will not be ejected.
def app_lib_except(app) do
["lib/#{app.name.underscore}/hidden_file.ex"]
end
@callback extra(app :: Uniform.App.t()) :: keyword()
A callback to add data to app.extra.
This callback exists for scenarios when you want to add an extra key to
many or all apps, and it can be programmatically determined from the
information in the app.
extra data that only applies to a single app usually belongs in the
Uniform Manifest.
example
Example
You may want to set the theme based on the name of the ejectable app. Return
a keyword list with a theme key. It will be available via
app.extra[:theme] in modify/2, base_files/1, and
templates.
def extra(app) do
theme =
case app.name.underscore do
"rebel_" <> _ -> :rebel
"empire_" <> _ -> :empire
_ -> raise "App name must start with rebel_ or empire_ to derive theme."
end
[theme: theme]
endThis prevents you from having to redundantly set
[
extra: [theme: :rebel]
]In every uniform.exs manifest.
uniform.exs has precedence
If
uniform.exsis[extra: [key: :manifest]],app.extra[:key]will (unsurprisingly) be:manifestin theextra/1callback.However, if
extra/1returns[key: :callback],app.extra[:key]will still be:manifestinmodify/2,base_files/1, and templates.In other words,
uniform.exs"has precedence over" theextra/1callback.
@callback target_path(path :: Path.t(), app :: Uniform.App.t()) :: Path.t()
Use this optional callback to change the path of files in the ejected codebase.
It will be called for every ejected file, with the exception that cp_r/2
will only call it once for the entire directory.
If you don't want to modify the path, just return it.
example
Example
You may want to place certain files in lib/ejected_app_web instead of
lib/ejected_app. Let's say you have an is_web_file? function that
identifies whether the file belongs in the _web directory. target_path
might look like this:
def target_path(path, app) do
if is_web_file?(path) do
# modify the path to put it in `lib/some_app_web`
String.replace(path, "lib/#{app.name.underscore}/", "lib/#{app.name.underscore}_web/")
else
path
end
end
Link to this section Functions
Inside of a deps do block, any Mix or Lib dependencies that should be
included in every ejected app should be wrapped in an always do block:
deps do
always do
# always eject the contents of `lib/some_lib`
lib :some_lib
# always eject the some_mix Mix dependency
mix :some_mix
end
end
The base_files section is where you specify files outside of an ejected
app's lib/my_app and test/my_app directories which should always be
ejected.
This section has access to an app variable which can
be used to build the instructions or conditionally include certain files with
if.
base_files do
# interpolating the app name into a path dynamically
cp "priv/static/images/#{app.name.underscore}_logo.png"
# conditional instructions
if deploys_to_fly_io?(app) do
template "fly.toml"
end
endNote that if conditionals cannot be nested here.
api-reference
API Reference
file/2ejects a single file or list of filestemplate/2creates a new file on ejection from an EEx templatecp/2copies a file (likefile/2) without running it through Code Transformations. This is useful for binary files such as images or executable.cp_r/2copies an entire directory of files without Code Transformations.
example
Example
base_files do
file ".credo.exs"
file "config/**/*.exs"
template "config/runtime.exs"
cp "bin/some-executable", chmod: 0o555
cp_r "assets"
end
files-in-lib
Files in lib
Typically, the base_files section only contains files that aren't in lib,
since files in lib/app_being_ejected and lib/required_lib_dependency are
ejected automatically.
# ❌ Don't do this
base_files do
file "lib/my_lib/some_file.ex"
end
# ✅ Instead, do this (lib/my_app/uniform.exs)
[
lib_deps: [:my_lib]
]
files-outside-lib-but-tied-to-lib-dependencies
Files outside lib but tied to Lib Dependencies
If a file or template should only be ejected in the case that a certain Lib
Dependency is included, we recommend placing that inside the deps section
instead of in base_files. (See Including files outside of
lib.)
# ❌ Don't do this
base_files do
if depends_on?(app, :lib, :my_lib) do
file "some_file"
end
end
# ✅ Instead, do this
deps do
lib :my_lib do
file "some_file"
end
end
cp works exactly like file/2, except that no Code
Transformations are applied to the file.
The file is copied as-is with File.cp!/3.
options
Options
cp takes a chmod option, which sets the mode for the file after it's
copied. See the possible permission
options.
examples
Examples
base_files do
# every ejected app will have bin/some-binary, with the ACL mode changed to 555
cp "bin/some-binary", chmod: 0o555
end
deps do
lib :pdf do
# apps that include the pdf lib will also have bin/convert
cp "bin/convert"
end
end
cp_r works like cp/2, but for a directory instead of a file. The
directory is copied as-is with File.cp_r!/3.
None of the files are ran through Code Transformations.
This is useful for directories that do not require modification, and contain many files.
For example, the assets/node_modules directory in a Phoenix application
would take ages to copy with file "assets/node_modules/**/*". Instead, use
cp_r "assets/node_modules".
examples
Examples
base_files do
cp_r "assets"
end
deps do
lib :some_lib do
cp_r "priv/files_for_some_lib"
end
end
Uniform automatically catalogs all Mix deps by looking into mix.exs to
discover all Mix deps. It also catalogs all Lib deps by scanning the lib/
directory.
If you need to configure anything about a Mix or Lib dep, such as other
dependencies that must be bundled along with it, use the deps block.
See mix/2, lib/2, and always/1 for more details.
example
Example
deps do
always do
lib :my_app do
only ["lib/my_app/application.ex"]
end
mix :phoenix
end
mix :absinthe do
mix_deps [:absinthe_plug, :dataloader]
end
lib :my_custom_aws_lib do
lib_deps [:my_utilities_lib]
mix_deps [:ex_aws, :ex_aws_ec2]
end
end
In the deps section of your Blueprint, you can specify that a Lib
Dependency excludes certain files.
This works much like the except option that can be given when importing
functions with import/2.
In the example below, for any app that depends on :aws, every file in
lib/aws and test/aws will be ejected except for lib/aws/hidden_file.ex.
deps do
lib :aws do
except ["lib/aws/hidden_file.ex"]
end
end
In base_files and lib blocks, file is used to specify files that are
not in a lib/ directory which should be ejected in the app or along with
the lib.
options
Options
chmod– Sets themodefor the givenfileafter it's ejected. See the possible permission options.match_dot– Forwarded toPath.wildcard/2. See "Wildcard Globs" below.
glob-expressions
Glob Expressions
You can use a glob expression with wildcard characters
to target multiple files. (See "Examples" below.)
This is possible because Uniform internally forwards the path and opts to
Path.wildcard/2 to determine which files to eject.
Note that the * and ? "wildcard characters" will not match files starting
with a dot (.) unless you provide match_dot: true.
examples
Examples
base_files do
file "assets/js/app.js"
file "some/file", chmod: 0o777
# glob targeting .exs files in config/
file "config/**/*.exs"
# glob targeting .exs files in priv/repo/ – including .formatter.exs
file "priv/repo/**/*.exs", match_dot: true
end
deps do
lib :aws do
# for every app that includes the aws lib dependency,
# some_aws_fixture.xml will also be included
file "test/support/fixtures/some_aws_fixture.xml"
end
end
Lib Dependencies are shared code
libraries in the lib directory of your project. Uniform scans lib/, so
you don't need to inform it about them.
However, there are four scenarios where you do need to list them in your Blueprint.
1-specifying-which-lib-dependencies-should-always-be-ejected
1. Specifying which Lib Dependencies should always be ejected
deps do
always do
# every app will have lib/utilities
lib :utilities
end
endSee always/1.
2-when-a-lib-dependency-has-other-lib-or-mix-dependencies
2. When a Lib Dependency has other Lib or Mix Dependencies
deps do
# if `lib/auth` is included, tesla and `lib/utils` will be included
lib :auth do
mix_deps [:tesla]
lib_deps [:utils]
end
endSee mix_deps/1 and lib_deps/1.
3-including-files-outside-of-lib
3. Including files outside of lib
Some libraries require other files outside of lib/that_library.
For example:
deps do
# when `lib/my_data_source is included...
lib :my_data_source do
# files in priv/my_data_source_repo will be included
file "priv/my_data_source_repo/**", match_dot: true
# .ex files in `test/support/my_data_source` will be included
file "test/support/my_data_source/**/*.ex"
# `priv/my_data_source_file` will be included from a template
template "priv/my_data_source_file"
end
endSee file/2, template/2, cp/1, and cp_r/1.
4-when-certain-files-should-be-excluded-from-a-lib-dependency-upon-ejection
4. When certain files should be excluded from a Lib Dependency upon ejection
deps do
always do
# every app will have lib/mix, but only `some_task.ex` will be ejected
lib :mix do
only ["lib/mix/tasks/some_task.ex"]
end
end
# `some_file.ex` will be omitted from `lib/auth` in ejected codebases
lib :auth do
except ["lib/auth/some_file.ex"]
end
end
Specify transitive Lib Dependencies of other Lib Dependencies.
Libraries listed with lib_deps will be included in the ejected codebase any
time the "parent" dependency is included.
examples
Examples
deps do
# `lib/core_auth` will never be ejected without `lib/oauth_lib`
lib :core_auth do
lib_deps [:oauth_lib]
end
end
Uniform automatically catalogues the deps in mix.exs, so there are only two
scenarios where you need to list them in your Blueprint.
1-specifying-deps-that-should-always-be-ejected
1. Specifying deps that should always be ejected.
deps do
always do
# every app will have credo and ex_doc
mix :credo
mix :ex_doc
end
endSee always/1.
2-when-a-mix-dependency-has-other-mix-dependencies
2. When a Mix Dependency has other Mix Dependencies.
deps do
mix :oban do
# any app that is ejected with oban will also have oban_pro and oban_web
mix_deps [:oban_pro, :oban_web]
end
endSee mix_deps/1.
Specify transitive Mix Dependencies of other Lib/Mix Dependencies.
Dependencies listed with mix_deps will be included in the ejected mix.exs
any time the "parent" dependency is included.
examples
Examples
deps do
# if absinthe is included, absinthe_plug and dataloader will be included
mix :absinthe do
mix_deps [:absinthe_plug, :dataloader]
end
# when `lib/ui_components` is included, surface will be included
lib :ui_components do
mix_deps [:surface]
end
end
@spec modify( pattern :: Path.t() | Regex.t(), function :: (file :: String.t(), Uniform.App.t() -> String.t()) | (file :: String.t() -> String.t()) ) :: term()
Modify the contents of one or more files during ejection.
Takes a transformation function which returns the new file contents as a string.
The first argument is either the relative path of a file in your Base
Project or a Regex.
# modify a specific test
modify "tests/path/to/specific_test.exs", fn file -> ... end
# modify all `_test.exs` files
modify ~r/_test.exs$/, fn file -> ... endThe second argument is either a function capture
modify ~r/.+_test.exs/, &MyApp.Modify.modify_tests/1
modify ~r/.+_test.exs/, &MyApp.Modify.modify_tests/2Or an anonymous function
modify ~r/.+_test.exs/, fn file ->
# ...
end
modify ~r/.+_test.exs/, fn file, app ->
# ...
endIf the function is 1-arity, it will receive the file contents. If it's
2-arity, it will receive the file contents and the Uniform.App struct.
examples
Examples
modify "config/config.exs", file do
file <>
~S'''
if config_env() in [:dev, :test] do
import_config "#{config_env()}.exs"
end
'''
end
modify ~r/.+_worker.ex/, fn file, app ->
String.replace(file, "SOME_VALUE_PER_APP", app.extra[:some_value])
end
In the deps section of your Blueprint, you can specify that a Lib
Dependency only includes certain files.
These work much like the only option that can be given when importing
functions with import/2.
In the example below, for any app that depends on :gcp, only
lib/gcp/necessary_file.ex will be ejected. Nothing else from lib/gcp or
test/gcp will be ejected.
deps do
lib :gcp do
# NOTHING in lib/gcp or test/gcp will be included except these:
only ["lib/gcp/necessary_file.ex"]
end
end
In base_files and lib blocks, template is used to specify EEx templates
that should be rendered and then ejected.
template-directory-and-destination-path
Template Directory and Destination Path
Uniform templates use a "convention over configuration" model that works like this:
At the top of your
Blueprintmodule, you specify a template directory like this:use Uniform, templates: "lib/uniform/templates"Templates must be placed in this directory at the relative path that they should be placed in, in the ejected directory.
Templates must have the destination filename, appended with
.eex.
Companion guide
Consult Building files from EEx templates for a more detailed look at constructing and effectively using templates for ejection.
options
Options
template takes a chmod option, which sets the mode for the rendered
file after it's ejected. See the possible permission
options.
examples
Examples
use Uniform, templates: "priv/uniform-templates"
base_files do
# priv/uniform-templates/config/runtime.exs.eex will be rendered, and the
# result will be placed in `config/runtime.exs` in every ejected app
template "config/runtime.exs"
end
deps do
lib :datadog do
# for every app that includes `lib/datadog`,
# priv/uniform-templates/datadog/prerun.sh.eex will be rendered, and
# the result will be placed in `datadog/prerun.sh`
template "datadog/prerun.sh", chmod: 0o555
end
end