Embedding Lua in the Web

Hello world

Starlight enables you to run Lua scripts in a webpage by placing Lua code in <script> tags. Here's a simple example:

<!DOCTYPE html>
<html>
<body>
 
  <script type="application/lua">
    print('Hello world')
  </script>
 
  <script src="//cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
  <script src="http://paulcuth.me.uk/starlight/browser-lib/starlight.js" data-run-script-tags></script>
 
</body>
</html>

Hello world

Run in JS Bin

As you can see we wrap the Lua code in a <script> tag with type="application/lua". This tells the browser not to parse it as JavaScript and also tells Starlight that it should parse and translate the code within.

We're also including the Babel browser runtime because Starlight outputs ES6 and, for the time being at least, most browsers do not fully support ES6. Hopefully over time the need to include Babel will reduce significantly. See also Using Starlight with Grunt to discover how to precompile your scripts and negate the need for Babel.

You'll also need to include Starlight itself. We're using the boolean attribute data-run-script-tags here to tell Starlight to execute the scripts on page load. Without this attribute the scripts will need to be executed manually.

That's all you need to get started. Notice that the print() function will output to the browser's console window. This can be modified in the Starlight configuration.

MIME types

Starlight will parse any <script> tags with the following types:

  • text/lua
  • text/x-lua
  • application/lua
  • application/x-lua

Including remote scripts

As with JavaScript, you can also include and execute Lua from remote scripts using the src attribute. One difference, however, is that files are loaded over XHR, so the remote scripts will need to adhere to CORS.

⋮
  <p class="result">0</p>
  <p><button>+1</button></p>
 
  <script type="application/lua" src="http://paulcuth.me.uk/starlight/lua/counter-app.lua"></script> 
 
  <script src="//cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
  <script src="http://paulcuth.me.uk/starlight/browser-lib/starlight.js" data-run-script-tags></script>
⋮

counter-app.lua

window.extract()
local counter = 0
 
local ui = {
  button = document:getElementsByTagName('button')[1],
  result = document:querySelector('.result'),
}
 
function updateUI ()
  ui.result.textContent = counter
end
 
function increment ()
  counter = counter + 1
  updateUI()
end
 
function init () 
  updateUI()
  ui.button:addEventListener('click', increment)
end
 
init()
Run in JS Bin

Scripts are loaded and parsed in the order that they appear in the page. You can add a defer attribute to defer the loading of the script until after others have loaded, so that your script does not block. The defer attribute is ignored on inline script tags.

Modules

You can also turn your script tag into a Lua module by giving it a data-modname attribute. This script will be preloaded but not executed and can later be required by any other Lua script. Make sure you define a module in a tag that precedes those that require it, in the order they appear in the page.

⋮
<script type="application/lua" data-modname="fibonacci">
  function fibonacci(n)
    a, b = 0, 1
    for i = 1, n do
      a, b = b, a + b
    end
    return a
  end
 
  return fibonacci
</script>
 
<script type="application/lua">
  local fib = require('fibonacci')
  print(fib(500))
</script>
⋮

1.394232245616977e+104

Run in JS Bin

The data-modname attribute can also be used on remote script tags. When using script tags, all modules need to be explicitly defined within the page; using require() with a relative file path will fail unless the required script is defined in the markup. This does not apply when you use Starlight to precompile your Lua to JavaScript, see Using Starlight with Grunt for more information.

⋮
  <p>
    <input value="20" />
    <button>Fibonacci</button>
  </p>
  <p class="result"></p>
 
  <script type="application/lua" src="http://paulcuth.me.uk/starlight/lua/fibonacci-module.lua" data-modname="fibonacci"></script>
  <script type="application/lua" src="http://paulcuth.me.uk/starlight/lua/fibonacci-app.lua"></script>
⋮
Run in JS Bin

fibonacci-module.lua / fibonacci-app.lua

Executing Lua programmatically from JavaScript

You can use Starlight to execute arbitrary Lua code from within JavaScript, using starlight.parser.parse().

⋮
<button id="click-me">Click me</button>
 
<script>
  var button = document.getElementById('click-me');
  button.addEventListener('click', function () {
    var lua = "print('Button clicked!!')";
    var handler = starlight.parser.parse(lua);
    handler();
  });
</script> 
⋮
Run in JS Bin

A nice way to use the parser in this manner is to place the Lua code in script tags and execute these on demand. In this case, remember to prevent your Lua scripts from executing on page load, by not adding the data-run-script-tags attribute.

⋮
<button id="click-me">Click me</button>
 
<script type="application/lua" id="click-handler">
  print('Button clicked!!')
</script>
 
<script>
  function init () {
    var lua = document.getElementById('click-handler').textContent;
    var handler = starlight.parser.parse(lua);
 
    var button = document.getElementById('click-me');
    button.addEventListener('click', handler);
  }
 
  window.addEventListener('load', init);
</script> 
Run in JS Bin

Configuration

Starlight can be configured by creating a configuration object in JavaScript before including the Starlight browser library.

The configuration object is window.starlight.config and can currently be used to override the destination of stdout and add variables to the Lua global namespace.

In the following example, we are redirecting the stdout to output to a DOM element instead of the browser's console window.

⋮
<pre id="output"></pre>
 
<script type="application/lua">
  print('Hello world')
</script>
 
<script>
  var outputEl = document.getElementById('output');
 
  window.starlight = {
    config: {
      stdout: {
        writeln: function () {
          var args = Array.prototype.splice.call(arguments, 0);
          outputEl.textContent += args.join('\t');
        }
      }
    }
  };
</script> 
Run in JS Bin

To use the configuration object to initialise the Lua global environment, see Interacting with JavaScript.