Treeshake (Popcorn v0.3.1)

View Source

Tree-shaker for compiled BEAM code. See run/1 for details.

Summary

Functions

Removes unused modules and functions from compiled BEAM code.

Types

keep_entry()

@type keep_entry() :: mfa() | module() | %{behaviour_impls: module()}

run_option()

@type run_option() ::
  {:ebin_files, [Path.t()]}
  | {:project, Path.t()}
  | {:output_dir, Path.t()}
  | {:dry_run, boolean()}
  | {:include_stdlibs, boolean()}
  | {:stub_removed_functions, boolean()}
  | {:stub_removed_modules, boolean()}
  | {:verbose, boolean()}
  | {:keep, [keep_entry()]}
  | {:drop, [module()]}
  | {:leave, [module()]}
  | {:ignore, [module() | mfa()]}

stats()

@type stats() :: %{
  modules_removed: [module()],
  modules_shaked: %{
    required(module()) => [{function :: atom(), arity :: non_neg_integer()}]
  },
  output_dir: Path.t(),
  module_index: %{required(module()) => module_info :: map()},
  call_graph: %{required(mfa()) => [mfa()]}
}

Functions

run(opts)

@spec run([run_option()]) :: stats()

Removes unused modules and functions from compiled BEAM code.

The removal is done by tree-shaking on the function level: reachable functions are found and all others are removed. The following functions are considered reachable:

  • start/2 functions of applications
  • functions called or captured (&Module.fun/arity) by a reachable function
  • functions referenced as a hard-coded module-function-arity (MFA) tuple or module-function-arguments tuple in a reachable function
  • callback implementations of behaviours, if their module is referenced in a reachable function
  • callback implementations of protocols, if their module is referenced and the protocol function is called in a reachable function

These cases cover vast majority, but not all used functions. For example, creating a module name dynamically from a string and calling a function from it may not make the function reachable, even though it's used. On the other hand, some unused functions may be considered reachable, for example when they're called by a reachable function, but from a branch of code that is never executed. Therefore, it's possible to manually inform the tree-shaker about reachability of particular modules and functions - see 'Manual adjustment of tree-shaking'.

Additionally, some modules are excluded from tree-shaking (left as is), but it doesn't impact reachability of their functions. See 'Modules left by default' for more details.

Accepted options

  • ebin_files - List of .beam and .app files to be tree-shaked. The beam files must have abstract code chunk in them (included by default by the compiler). This option is mandatory, unless project option is provided.
  • project - Path to the root directory of a project to be tree-shaked. The project must be compiled with MIX_ENV=prod. If ebin_files are also passed, they are included as well.
  • output_dir - Path to the directory where the tree-shaked ebin files are stored. Mandatory, unless dry_run is set to true.
  • dry_run - If true, no output is written.
  • include_stdlibs - If true, Erlang and Elixir standard libraries are included and tree-shaked, making the output self-contained. The following applications are included: compiler, elixir, erts, kernel, logger, stdlib. Defaults to false.
  • stub_removed_functions - If true, replace removed functions with stubs that raise an error. Defaults to false.
  • stub_removed_modules - If true, replace removed modules with empty stubs. Has no effect when stub_removed_functions is set to true. Defaults to false.
  • verbose - Print status logs.

Manual adjustment of tree-shaking

Additionally, the following options configure the tree-shaking behavior. They all default to an empty list.

  • keep - List of public functions that must not be removed along with all code they rely on. Passing a module means 'all functions from this module', passing %{benaviour_impls: behaviour} means 'all modules implementing this behaviour'. Applications' start/2 functions are automatically added to this list.
  • drop - List of modules to be removed regardless of the tree-shaking results
  • leave - List of modules that must not be changed or removed, but without affecting the tree-shaking process. It means that the code they rely on may still be removed unless added to keep or leave.
  • ignore - List of modules or functions that are not analyzed by the tree-shaker. It means they can be tree-shaked as usual, but the code they rely on may be tree-shaked even if they're kept, as it is unknown to the tree-shaker. This option accepts also private functions. Note that if an ignored function calls a private function and that function gets tree-shaked, the result won't compile and tree-shaking will fail. When a module is in both leave and ignore lists, it's not read and it's copied to the output without changes.

Modules left by default

Some stdlib modules are hard to tree-shake and they're therefore left by default. The behaviour is the same as if they were in the leave list. These modules can still be removed if added to drop or ignore list.

The modules from the following stdlib apps: erts, kernel, logger, stdlib, are left by default, except the following modules: beam_lib, dets, dets_utils, dets_v9, disk_log, edlin_expand, epp, erl_parse, erl_pp, erl_scan, erl_tar, erlang, file_sorter, global, inet, lists, ms_transform, net_kernel, prim_inet, qlc, qlc_pt, rand, sofs, string, unicode_util, uri_string, zip.

Returned value

This function returns a map with the following keys:

  • modules_removed - list of modules completely removed due to tree-shaking
  • modules_shaked - a map where each key is a module and value is a list of the functions removed from this module
  • output_dir - the directory where output ebin files were written
  • module_index - information about the tree-shaked modules, for debugging purposes
  • call_graph - graph of calls within tree-shaked modules, for debugging purposes

Note that when stub_removed_functions or stub_removed_modules is set to true, modules_removed and modules_shaked contain stubbed modules and functions.