Skip to main content
The minifier is an experimental feature. The API may change in future versions.

What is the Minifier?

The minifier reduces Lua code size by:
  1. Removing unnecessary whitespace and comments (trivia)
  2. Renaming variables to shorter names while preserving program behavior
  3. Respecting scope rules to avoid naming conflicts
Minification is commonly used to:
  • Reduce file size for distribution
  • Decrease bandwidth usage
  • Provide basic code obfuscation
  • Optimize code for embedded systems

Basic Usage

Use the Minify extension method on any SyntaxTree:
using Loretta.CodeAnalysis.Lua;
using Loretta.CodeAnalysis.Lua.Experimental;

var code = @"
-- Calculate factorial
local function factorial(number)
    if number <= 1 then
        return 1
    else
        return number * factorial(number - 1)
    end
end

local result = factorial(5)
print('Factorial of 5 is:', result)
";

var tree = LuaSyntaxTree.ParseText(code);
var minified = tree.Minify();

Console.WriteLine(minified.GetRoot().ToFullString());
// Output: local function a(b)if b<=1 then return 1 else return b*a(b-1)end end local c=a(5)print('Factorial of 5 is:',c)

Minifier API

The LuaExtensions class provides three overloads of the Minify method:

Simple Minification

Uses default settings (alphabetical naming strategy with sorted slot allocation):
var minified = tree.Minify();

Custom Naming Strategy

Specify how variables should be renamed:
using Loretta.CodeAnalysis.Lua.Experimental.Minifying;

var minified = tree.Minify(NamingStrategies.Numerical);

Full Control

Customize both naming and slot allocation:
using Loretta.CodeAnalysis.Lua.Experimental.Minifying;

var minified = tree.Minify(
    NamingStrategies.Alphabetical,
    new SequentialSlotAllocator()
);

Naming Strategies

Naming strategies determine how variable slots are converted to names. The NamingStrategies class provides several built-in options:

Alphabetical (Default)

Uses lowercase letters a-z with underscore prefix for conflicts:
var minified = tree.Minify(NamingStrategies.Alphabetical);
Example sequence: a, b, c, …, z, aa, ab, ac, … If a name conflicts with an existing variable or keyword, it adds an underscore prefix: _a, _b, etc.

Numerical

Uses digits 0-9 with underscore prefix:
var minified = tree.Minify(NamingStrategies.Numerical);
Example sequence: _0, _1, _2, …, _9, _00, _01, …
Numbers cannot start Lua identifiers, so numerical names always have at least one underscore prefix.

ZeroWidth

Uses zero-width Unicode characters for maximum size reduction:
var minified = tree.Minify(NamingStrategies.ZeroWidth);
Only works with LuaJIT! Standard Lua interpreters may not support zero-width characters in identifiers.
Characters used:
  • Prefix: Zero Width Space (\u200B)
  • Alphabet: Zero Width Non-Joiner (\u200C), Zero Width Joiner (\u200D), Zero Width No-Break Space (\uFEFF)

Custom Naming Strategy

Create your own naming strategy using the Sequential factory method:
var customStrategy = NamingStrategies.Sequential(
    prefix: '_',
    alphabet: ImmutableArray.Create("x", "y", "z")
);

var minified = tree.Minify(customStrategy);
Or implement the NamingStrategy delegate directly:
string CustomNaming(int slot, IEnumerable<IScope> scopes)
{
    // Your custom logic here
    return $"var{slot}";
}

var minified = tree.Minify(CustomNaming);

Slot Allocators

Slot allocators determine how variable slots are assigned and reused. The experimental package provides two implementations:

SortedSlotAllocator (Default)

Reuses variable names when possible by tracking which slots are available:
var minified = tree.Minify(
    NamingStrategies.Alphabetical,
    new SortedSlotAllocator()
);
Advantages:
  • Maximum name reuse
  • Smallest possible variable names
  • Better minification ratio
Disadvantages:
  • Slightly slower due to slot tracking

SequentialSlotAllocator

Assigns slots sequentially without reuse:
var minified = tree.Minify(
    NamingStrategies.Alphabetical,
    new SequentialSlotAllocator()
);
Advantages:
  • Faster allocation
  • Simpler logic
Disadvantages:
  • No name reuse
  • May produce longer names

Custom Slot Allocator

Implement the ISlotAllocator interface:
public class MyAllocator : ISlotAllocator
{
    public int AllocateSlot()
    {
        // Return a slot number
    }

    public void ReleaseSlot(int slot)
    {
        // Mark slot as available for reuse
    }
}

var minified = tree.Minify(
    NamingStrategies.Alphabetical,
    new MyAllocator()
);

Transformations Performed

The minifier performs two main transformations:

1. Trivia Removal

Removes all unnecessary whitespace and comments while preserving required separators:
-- Before
local x = 1  -- comment
local y = 2

function foo()
    return x + y
end

-- After  
local x=1 local y=2 function foo()return x+y end
A space is inserted between tokens when required by Lua syntax. For example, return1 would be invalid, so it becomes return 1.

2. Variable Renaming

Renames local variables and function parameters to shorter names:
-- Before
local playerHealth = 100
local playerName = "Hero"

local function healPlayer(amount)
    playerHealth = playerHealth + amount
end

-- After
local a=100 local b="Hero" local function c(d)a=a+d end
What gets renamed:
  • Local variables
  • Function parameters
  • Local function names
What doesn’t get renamed:
  • Global variables (preserved to maintain external API)
  • Table field names (preserved to maintain data structure)
  • String literals (preserved as data)

Complete Example

using Loretta.CodeAnalysis.Lua;
using Loretta.CodeAnalysis.Lua.Experimental;
using Loretta.CodeAnalysis.Lua.Experimental.Minifying;
using System;

var code = @"
-- Player management system
local playerData = {
    health = 100,
    mana = 50,
    level = 1
}

local function takeDamage(amount)
    playerData.health = playerData.health - amount
    if playerData.health <= 0 then
        print('Player died!')
    end
end

local function useMana(cost)
    if playerData.mana >= cost then
        playerData.mana = playerData.mana - cost
        return true
    end
    return false
end

takeDamage(30)
useMana(10)
";

var tree = LuaSyntaxTree.ParseText(code);

Console.WriteLine("Original size: " + code.Length + " bytes");
Console.WriteLine("\nOriginal code:");
Console.WriteLine(code);

var minified = tree.Minify();
var minifiedCode = minified.GetRoot().ToFullString();

Console.WriteLine("\nMinified size: " + minifiedCode.Length + " bytes");
Console.WriteLine("Reduction: " + (100 - (minifiedCode.Length * 100.0 / code.Length)).ToString("F1") + "%");
Console.WriteLine("\nMinified code:");
Console.WriteLine(minifiedCode);
Output:
Original size: 456 bytes

Original code:
-- Player management system
local playerData = {
    health = 100,
    mana = 50,
    level = 1
}
...

Minified size: 187 bytes
Reduction: 59.0%

Minified code:
local a={health=100,mana=50,level=1}local function b(c)a.health=a.health-c if a.health<=0 then print('Player died!')end end local function d(e)if a.mana>=e then a.mana=a.mana-e return true end return false end b(30)d(10)

Trade-offs and Considerations

Advantages

  • Smaller file size: Typically 40-70% reduction
  • Faster downloads: Less bandwidth usage
  • Basic obfuscation: Makes code harder to read
  • Performance: Minimal impact on runtime (slightly faster parsing)

Disadvantages

  • Loss of readability: Debugging becomes very difficult
  • No source maps: Hard to map errors back to original code
  • Irreversible: Cannot recover original variable names
  • Limited obfuscation: Not a security measure; can be reverse-engineered

Best Practices

Always keep your original source code! Minification is a one-way transformation.
  1. Minify only for distribution: Keep development code readable
  2. Version control: Commit source code, not minified code
  3. Test thoroughly: Ensure minified code behaves identically
  4. Document external APIs: Global variables and table structures should be documented
  5. Consider source maps: For production debugging (requires custom implementation)

Use Cases

Good Use Cases

  • Lua scripts in games: Reduce size of embedded scripts
  • Web-delivered Lua: Minimize bandwidth for browser-based Lua engines
  • ROM-constrained devices: Save space on embedded systems
  • Release builds: Optimize production code
  • Basic IP protection: Make code harder to read (not secure!)

When to Avoid

  • Development: Keep code readable during development
  • Open source: If you want others to learn from your code
  • Debugging: When you need stack traces and readable errors
  • Collaborative projects: Team members need to understand the code

Combining with Constant Folding

For maximum optimization, combine constant folding with minification:
using Loretta.CodeAnalysis.Lua;
using Loretta.CodeAnalysis.Lua.Experimental;

var code = @"
local x = 10 + 5
local y = x * 2
print(y)
";

var tree = LuaSyntaxTree.ParseText(code);

// First fold constants
var folded = tree.GetRoot().ConstantFold(ConstantFoldingOptions.Default);
var foldedTree = tree.WithRootAndOptions(folded, tree.Options);

// Then minify
var minified = foldedTree.Minify();

Console.WriteLine(minified.GetRoot().ToFullString());
// Output: local a=15 local b=a*2 print(b)

See Also

Build docs developers (and LLMs) love