Tutorial: External Modules in Corona

Share on Facebook0Share on Google+4Tweet about this on TwitterShare on LinkedIn2

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 Storyboard scenes or even custom modules of your own.

Today I’m going to 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 the code in your modules is executed, including what code is not run when you call the built-in require() function.

Including External Modules

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 has an entire file all to itself.

Here’s a really simple module, example1.lua, which simply prints a statement to the Terminal/console and returns an empty table:

Now, in a different module — let’s just use main.lua — require the example1.lua module and see what happens. Note that all code samples in this tutorial assume that the custom external modules are placed in the top level of your project folder (same location as main.lua).

As expected, the variable ex1 is equal to the blank table we returned in example1.lua, and the words “example1.lua has been loaded.” appear in the Terminal/console. On the next line, we assign a trivial property to the ex1 table.

Pay close attention now since this gets more tricky. Let’s require example1.lua in a separate module, scene1.lua, after we have already required the module in main.lua, and see what happens:

This time — in scene1.lua — when you require the example1.lua module, the words “example1.lua has been loaded.” did not appear in the Terminal. However, when you print the value of ex1.testvar, the value of “Hello World.” is printed to the Terminal, which means that the testvar property exists.


What can we learn from this? Well, the reason “example1.lua has been loaded.” did not appear in the Terminal is because the module has already been loaded by main.lua. Once a module is loaded, the 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.

When you call require(), the first thing that happens is the package.loaded table is checked to see if the module has been loaded previously. If it’s found, then instead of re-requiring the external module, the stored return value in package.loaded is returned. This returned value is a reference, not a copy. So if your module returns a table, that’s the same table you’ll be getting (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.” was not shown again. When require() is called, the module is not re-loaded if it already exists in the global package.loaded table.

Executing Module Code

There are two ways to get code in a module to run:

  1. Put the code in a function (within the module), and call it like a normal function.
  2. Remove the module from package.loaded table and re-require it (generally not recommended).

Here’s an example of the first scenario:

As you can see from the example, we require‘d example2.lua in two different modules: main.lua and scene1.lua. The first print statement (outside of any functions) was shown only once, which is the first time the module was required. The second print statement, the one within the hello() function, was shown each time the hello() function was called.

The lesson learned: attach functions to the table you return at the end of the module if you want that code to be run more than once, or if you don’t want it to be run immediately upon requiring the module.

Removing Modules from “package.loaded”

As stated above, you require a module like example2.lua using the following syntax:

The return value of example2.lua is then stored in the package.loaded table under:

To clear a module from memory entirely, simply assign nil to this reference as follows:

At this point, the module will be cleared from memory and all of its associated function(s) will cease to exist.

This is Universal

When dealing with any module, whether it’s your own custom-made module, a module you downloaded from the code exchange, or even built-in modules, all of the rules above apply.

The same rules apply to Storyboard scenes which explains why some code is executed when a scene is loaded and why some is not, including the code that executes within the various “scene state” functions of Storyboard.

Share on Facebook0Share on Google+4Tweet about this on TwitterShare on LinkedIn2

This entry has 13 replies

  1. Dave Baxter says:

    Very interesting stuff and as you say makes understanding Storyboard a whole lot easier.

    I have some standard code in all of my scenes, things like detecting swipes, button presses etc… Can all this be put in a external module and then I just require the module in each scene and call the functions ? Would I actually be able to assign the functions to touch events or is that a little dangerous ?


    • Jonathan Beebe says:

      Hi Dave, yes, you can definitely do that. In fact, if your project gets big, it’s actually recommended as it makes organizing things easier (and keeps your Lua scripts lean).

  2. Tahir says:

    What happens if I require a module that does not return any thing? What would go in package.loaded table?

  3. owenyang says:

    awesome lesson

  4. srdjan says:

    Does this mean that we can define require file which can be holder for variables used thru the whole game? Variable holder with functions…that will be uber cool!
    No need for globals at all.

    Can’t wait to see Modules and the Storyboard post 🙂

  5. Will K says:

    On this topic, can you explain this:

    module(…, package.seeall)

    And related, can you explain the difference between using new() to instantiate an object from a module, and just including it? In other languages, that pattern is clearer, you create a class, put that in an external file, include the file and instantiate the object of the class. But in lua, I’m vague, there seem to be two styles of doing this. Older styles seem to use new() and more recent don’t bother. Can you provide a best practice example for the community to follow?

    Thank you very much!

    • Walter says:

      @Will, module() was the old way of creating modules. It’s fallen out of favor for a variety of reasons, so much so that I believe it’s been removed from Lua 5.2.

  6. Naomi says:

    Thank you for the detailed and clear explanation of how requiring a module works. Very helpful.

    Although this tutorial is not about Storyboard API, now I’m wondering how it may relate to it. For example, when dose a scene get fully unloaded from memory? I mean, let’s say, I transition to scene1.lua from main.lua, and then I may move on to scene2.lua — at what point will package.loaded[“scene1”] = nil be called (assuming it is handled by the API.) Does it get called upon leaving scene1.lua if I have storyboard.purgeOnSceneChange set to true?

  7. Robert says:

    Thanks, its great to have module loading explained succinctly. This tutorial, combined with the other tutorials you’ve provided on use of external modules has been very helpful.

  8. Nick says:

    How do you access objects inside a function?

    Example: I want to call a table from another module, so I store it inside a function. Now I want to access that table key and value to remove or alter. How do I reference it?

  9. Kamz says:

    Results in Runtime error. Tried the sample project on Github as well as the steps mentioned above in custom project. Same result.

    Doesn’t work with the latest build (Build: 2016.2839). Am on Windows 7. Simulator gives the below errors. While I understand this should be tested on device, it should not stop the flow of the program (similar to the ads plugin).

    09:39:35.378 module ‘plugin_combre’ not found:
    09:39:35.378 no field package.preload[‘plugin_combre’]
    09:39:35.378 no file ‘C:\Users\142074\AppData\Roaming\Corona Labs\Corona Simulator\Plugins\plugin_combre.lua’
    09:39:35.378 no file ‘D:\MaathiYosi\plugin_combre.lua’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\Resources\plugin_combre.lua’
    09:39:35.378 no file ‘.\plugin_combre.lua’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\lua\plugin_combre.lua’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\lua\plugin_combre\init.lua’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\plugin_combre.lua’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\plugin_combre\init.lua’
    09:39:35.378 no file ‘C:\Users\142074\AppData\Roaming\Corona Labs\Corona Simulator\Plugins\plugin_combre.dll’
    09:39:35.378 no file ‘.\plugin_combre.dll’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\Resources\plugin_combre.dll’
    09:39:35.378 no file ‘.\plugin_combre.dll’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\plugin_combre.dll’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\loadall.dll’
    09:39:35.378 no file ‘C:\Users\142074\AppData\Roaming\Corona Labs\Corona Simulator\Plugins\plugin_combre.dll’
    09:39:35.378 no file ‘.\plugin_combre.dll’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\Resources\plugin_combre.dll’
    09:39:35.378 no file ‘.\plugin_combre.dll’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\plugin_combre.dll’
    09:39:35.378 no file ‘D:\Program Files\Corona Labs\Corona SDK\loadall.dll’

    • Rob Miracle says:

      I’m not sure what you’re post/sample app your talking about. If you’re having problems with plugins, I recommend asking your question in the forums. It’s likely we will need to see code and code doesn’t post well in comments.