There’s often confusion as to what exactly happens when external modules are “required” into your code, which leads to further confusion and unexpected behavior when it comes to things such as Composer scenes or even custom modules of your own.
This tutorial will guide you through a series of exercises (with explanations) that should illustrate exactly how modules work in Lua, so you get a full understanding of when (and which) code in your modules is executed.
In its simplest form, an external module is simply a Lua file that returns something, most likely a table. It can almost be thought of as a function definition, but it’s contained in a file all to itself.
Here’s a really simple module, example1.lua
, which simply prints a statement to the console and returns an empty table:
-- example1.lua local M = {} print( "example1.lua has been loaded" ) return M
Now, in a different module — let’s just use main.lua
— require the example1.lua
module and see what happens (note that all code examples in this tutorial assume that the custom external modules are placed in the root level of your project folder, alongside main.lua
).
-- main.lua local ex1 = require( "example1" ) ex1.testvar = "Hello World!"
As expected, upon execution of the first command, the variable ex1
becomes equal to the empty table (M
) which we created and returned in example1.lua
, and the words example1.lua has been loaded
In the next command, we assign a trivial property, testvar
, to this ex1
table.
Pay close attention now since this gets more tricky. Let’s require example1.lua
in an another module, scene1.lua
, after we have already required it within main.lua
:
-- scene1.lua (previous "main.lua" still applies) local examp1 = require( "example1" ) print( examp1.testvar )
This time — in scene1.lua
— when you require the example1.lua
module, the words example1.lua has been loaded
examp1.testvar
, Hello World!
testvar
property exists! What can we learn from this? Read on…
The reason why example1.lua has been loaded
main.lua
. Essentially, when a module is loaded (required), its code is executed from top to bottom as usual, and the return value of the module is stored in a global table called package.loaded
. From that point on, the module is considered “loaded” and it can’t simply be
Let’s explore this in a bit more detail:
When you call require()
, the first thing that happens is the package.loaded
table is checked to see if the module was previously loaded.
If it is found, then instead of package.loaded
is returned. In other words, this returned value is a reference, not a copy. Thus, if your module returns a table, that’s the same table you’ll get (properties and all) when you call require()
on the same module in the future.
If the module is not found in package.loaded
, the module will be loaded, the code will run from top to bottom, and the return value of the module will be stored in the package.loaded
table for any future requires of the module. This explains why example1.lua has been loaded
example1.lua
was already required within main.lua
, it is not scene1.lua
.
There are two ways to get code within a module to run:
Put the code inside a function (within the module) and call it like a normal function.
Remove the module from the package.loaded
table and
Here’s an example of the first scenario:
-- example2.lua local M = {} print( "example2.lua has been loaded" ) M.hello = function() print( "Hello World!" ) end return M
-- main.lua local ex2 = require( "example2" ) --> example2.lua has been loaded ex2.hello() --> Hello World!
-- scene1.lua local examp2 = require( "example2" ) examp2.hello() --> Hello World!
As you can see, we required example2.lua
in two different modules, main.lua
and scene1.lua
. The first print()
statement — the one outside of any functions — occurs only once (the first time the module is required within main.lua
). In contrast, the second print()
statement — the one within the M.hello
function — is executed each time that function is called.
What is the lesson learned? Essentially, if you want to run code within a module more than once, or if you don’t want to run it immediately upon requiring the module, you should wrap it within a function and “attach” the function to the table which you return at the end of the module.
Properly “attaching” module functions to the returned table is essential to gain access to them. You can not simply include the function within the module as a local function and then expect access to it from another module. Notice the difference:
local M = {} -- This function WILL be accessible outside of this module, wherever the module is required M.hello = function() print( "Hello World!" ) end -- This function will NOT be accessible outside of this module, only within it locally local hello = function() print( "Hello World!" ) end return M
As outlined above, you require a module like example2.lua
using the following syntax:
local ex2 = require( "example2" )
With this, the return value of example2.lua
is effectively stored in the package.loaded
table under:
package.loaded["example2"]
Thus, if you want to nil
to this reference as follows:
package.loaded["example2"] = nil
At this point, the module will be cleared from memory and all of its associated function(s) will cease to exist.
Hopefully this tutorial has given you a solid foundational understanding of Lua modules, how to include them, and how to call functions that exist in one module from another module. Conveniently, these rules are universal and apply to any module you may be dealing with, whether it’s your own