diff --git a/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc b/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b2c3127aa97ce23e229ac651ca7b6c7ae7c6a5fe
--- /dev/null
+++ b/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc
@@ -0,0 +1,39 @@
+#include "../CRTPLuaScriptObject.hh"
+#include "lua.hpp"
+#include <QFileInfo>
+#include <QString>
+
+using namespace Vivy::Script;
+
+int
+FreeFunctions::print(lua_State *const L) noexcept
+{
+    const std::string_view arg = CHECK_STRING_VIEW(L, 1);
+    out(LuaContext::getContext(L)) << "OUT: " << arg << "\n";
+    LUA_RETURN_NOTHING(L);
+}
+
+int
+FreeFunctions::getModule(lua_State *const L) noexcept
+{
+    const auto *const context                           = LuaContext::getContext(L);
+    const std::string_view modName                      = CHECK_STRING_VIEW(L, 1);
+    [[maybe_unused]] const ModuleDeclaration *const mod = context->getModule(modName);
+    return 1;
+}
+
+int
+FreeFunctions::start(lua_State *const L) noexcept
+{
+    lua_settop(L, 0);
+    lua_pushinteger(L, 0);
+    return 1;
+}
+
+int
+FreeFunctions::finish(lua_State *const L) noexcept
+{
+    lua_settop(L, 0);
+    lua_pushinteger(L, 0);
+    return 1;
+}
diff --git a/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4612f0316fd8e0eb770eb3f0c55db17aa3037492
--- /dev/null
+++ b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc
@@ -0,0 +1,32 @@
+#include "../CRTPLuaScriptObject.hh"
+#include "lua.hpp"
+#include <QFileInfo>
+#include <QString>
+
+using namespace Vivy::Script;
+
+int
+FunctionDeclaration::TOSTRING(lua_State *const L) noexcept
+{
+    FunctionDeclaration *const self = CHECK(L, 1);
+    std::string str(className);
+    str += std::string_view(" { ");
+    str += self->parentScript.empty() ? self->name : self->parentScript + "::" + self->name;
+    str += std::string_view(" }");
+    lua_pushstring(L, str.c_str());
+    return 1;
+}
+
+int
+FunctionDeclaration::setName(lua_State *L) noexcept
+{
+    FunctionDeclaration::CHECK(L, 1)->name = CHECK_STRING_VIEW(L, 2);
+    LUA_RETURN_NOTHING(L);
+}
+
+int
+FunctionDeclaration::setImportFromScript(lua_State *L) noexcept
+{
+    FunctionDeclaration::CHECK(L, 1)->parentScript = CHECK_STRING_VIEW(L, 2);
+    LUA_RETURN_NOTHING(L);
+}
diff --git a/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc
new file mode 100644
index 0000000000000000000000000000000000000000..52e0d9be2de5f2e99323edeaa1a82ecd344495eb
--- /dev/null
+++ b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc
@@ -0,0 +1,63 @@
+#include "../CRTPLuaScriptObject.hh"
+#include "lua.hpp"
+#include <QFileInfo>
+#include <QString>
+
+using namespace Vivy::Script;
+
+int
+JobDeclaration::TOSTRING(lua_State *const L) noexcept
+{
+    JobDeclaration *const self = CHECK(L, 1);
+    std::string str(className);
+    str += std::string_view(" { ");
+
+    str += self->parentScript.empty() ? self->name : self->parentScript + "::" + self->name;
+    str += ", options: [";
+
+    for (const OptionDeclaration *const opt : self->options) {
+        for (auto const &[name, _] : opt->options)
+            str += " " + name;
+    }
+
+    str += std::string_view(" ] }");
+    lua_pushstring(L, str.c_str());
+    return 1;
+}
+
+int
+JobDeclaration::setName(lua_State *L) noexcept
+{
+    JobDeclaration::CHECK(L, 1)->name = CHECK_STRING_VIEW(L, 2);
+    LUA_RETURN_NOTHING(L);
+}
+
+int
+JobDeclaration::setOptions(lua_State *L) noexcept
+{
+    auto *const context       = LuaContext::getContext(L);
+    JobDeclaration *const job = JobDeclaration::CHECK(L, 1);
+    err(context) << "Set options for job '" << job->name << "'\n";
+
+    IterateOverArray(L, 2, [&context, job, L]() noexcept -> bool {
+        OptionDeclaration *opt = OptionDeclaration::CHECK(L, -1);
+
+        for (const auto &[name, value] : opt->options) {
+            err(context) << "\tregister option \"" << name << "\" with type \""
+                         << value.getSignature() << "\"\n";
+        }
+
+        job->options.push_back(opt);
+        job->addUserValue(L, 1); // Tie T[i] to job and pop stack
+        return true;             // Value was popped from the stack
+    });
+
+    LUA_RETURN_NOTHING(L);
+}
+
+int
+JobDeclaration::setImportFromScript(lua_State *L) noexcept
+{
+    JobDeclaration::CHECK(L, 1)->parentScript = CHECK_STRING_VIEW(L, 2);
+    LUA_RETURN_NOTHING(L);
+}
diff --git a/src/Lib/Script/CRTPLuaScriptObject.cc b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
similarity index 63%
rename from src/Lib/Script/CRTPLuaScriptObject.cc
rename to src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
index 083a7ea5d73abace4d40124c1d203c471c5ab408..6d7b048fe94c80c12184f4584fc0d0661739e9f2 100644
--- a/src/Lib/Script/CRTPLuaScriptObject.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
@@ -1,47 +1,10 @@
-#include "CRTPLuaScriptObject.hh"
+#include "../CRTPLuaScriptObject.hh"
 #include "lua.hpp"
 #include <QFileInfo>
 #include <QString>
 
 using namespace Vivy::Script;
 
-// FreeFunctions
-
-int
-FreeFunctions::print(lua_State *const L) noexcept
-{
-    const std::string_view arg = CHECK_STRING_VIEW(L, 1);
-    out(LuaContext::getContext(L)) << "OUT: " << arg << "\n";
-    LUA_RETURN_NOTHING(L);
-}
-
-int
-FreeFunctions::getModule(lua_State *const L) noexcept
-{
-    const auto *const context                           = LuaContext::getContext(L);
-    const std::string_view modName                      = CHECK_STRING_VIEW(L, 1);
-    [[maybe_unused]] const ModuleDeclaration *const mod = context->getModule(modName);
-    return 1;
-}
-
-int
-FreeFunctions::start(lua_State *const L) noexcept
-{
-    lua_settop(L, 0);
-    lua_pushinteger(L, 0);
-    return 1;
-}
-
-int
-FreeFunctions::finish(lua_State *const L) noexcept
-{
-    lua_settop(L, 0);
-    lua_pushinteger(L, 0);
-    return 1;
-}
-
-// ModuleDeclaration
-
 bool
 ModuleDeclaration::resolvModules(LuaContext *const context) noexcept
 {
@@ -72,15 +35,14 @@ ModuleDeclaration::importMethod(lua_State *const L) noexcept
     // Import a module
     err(context) << "Importing a module...";
     const ModuleDeclaration *const module = context->getModule(mod);
-    if (module) {
-        err(context) << "Loaded module " << module->moduleName << "\n";
-    } else {
+    if (module == nullptr) {
         err(context) << "Failed to load module " << mod << "\n";
-        return 1;
+        return 1; // Stack is { top: nil, str, table, self }
     }
+
     // Stack is { top: module, str, table, self }
 
-    // Import a function
+    // Import a function if needed
     if (lua_rawlen(L, 2) == 2) {
         lua_pushinteger(L, 2); // Stack is { top: int, module, str, table, self }
         lua_gettable(L, 2);    // Stack is { top: str, module, str, table, self }
@@ -285,136 +247,3 @@ ModuleDeclaration::validateModule(lua_State *const L) const noexcept
 
     return true;
 }
-
-// OptionDeclaration
-
-int
-OptionDeclaration::TOSTRING(lua_State *const L) noexcept
-{
-    OptionDeclaration *const self = CHECK(L, 1);
-    std::string str(className);
-    str += std::string_view(" { ");
-
-    for (auto const &[name, opt] : self->options) {
-        str += "\"" + name + "\" -> ";
-        str += opt.getSignature();
-        str += ", ";
-    }
-
-    str.resize(str.size() - 2); // Delete the leading ", "
-    str += std::string_view(" }");
-    lua_pushstring(L, str.c_str());
-    return 1;
-}
-
-int
-OptionDeclaration::addOptionContent(lua_State *const L) noexcept
-{
-    OptionDeclaration *const self = CHECK(L, 1);
-    luaL_checktype(L, 2, LUA_TSTRING);
-
-    const std::string_view optName = CHECK_STRING_VIEW(L, 2);
-    const int valueType            = lua_type(L, 3);
-
-    // XXX: Add support for other types!
-    if (valueType == LUA_TNUMBER)
-        self->options.emplace(optName, lua_tonumber(L, 3));
-
-    else if (valueType == LUA_TSTRING)
-        self->options.emplace(optName, CHECK_STRING_VIEW(L, 3));
-
-    else if (valueType == LUA_TBOOLEAN)
-        self->options.emplace(optName, static_cast<bool>(lua_toboolean(L, 3)));
-
-    else
-        luaL_error(L, "Unsupported type %s", luaL_typename(L, 3));
-
-    LUA_RETURN_NOTHING(L);
-}
-
-// JobDeclaration
-
-int
-JobDeclaration::TOSTRING(lua_State *const L) noexcept
-{
-    JobDeclaration *const self = CHECK(L, 1);
-    std::string str(className);
-    str += std::string_view(" { ");
-
-    str += self->parentScript.empty() ? self->name : self->parentScript + "::" + self->name;
-    str += ", options: [";
-
-    for (const OptionDeclaration *const opt : self->options) {
-        for (auto const &[name, _] : opt->options)
-            str += " " + name;
-    }
-
-    str += std::string_view(" ] }");
-    lua_pushstring(L, str.c_str());
-    return 1;
-}
-
-int
-JobDeclaration::setName(lua_State *L) noexcept
-{
-    JobDeclaration::CHECK(L, 1)->name = CHECK_STRING_VIEW(L, 2);
-    LUA_RETURN_NOTHING(L);
-}
-
-int
-JobDeclaration::setOptions(lua_State *L) noexcept
-{
-    auto *const context       = LuaContext::getContext(L);
-    JobDeclaration *const job = JobDeclaration::CHECK(L, 1);
-    err(context) << "Set options for job '" << job->name << "'\n";
-
-    IterateOverArray(L, 2, [&context, job, L]() noexcept -> bool {
-        OptionDeclaration *opt = OptionDeclaration::CHECK(L, -1);
-
-        for (const auto &[name, value] : opt->options) {
-            err(context) << "\tregister option \"" << name << "\" with type \""
-                         << value.getSignature() << "\"\n";
-        }
-
-        job->options.push_back(opt);
-        job->addUserValue(L, 1); // Tie T[i] to job and pop stack
-        return true;             // Value was popped from the stack
-    });
-
-    LUA_RETURN_NOTHING(L);
-}
-
-int
-JobDeclaration::setImportFromScript(lua_State *L) noexcept
-{
-    JobDeclaration::CHECK(L, 1)->parentScript = CHECK_STRING_VIEW(L, 2);
-    LUA_RETURN_NOTHING(L);
-}
-
-// FunctionDeclaration
-
-int
-FunctionDeclaration::TOSTRING(lua_State *const L) noexcept
-{
-    FunctionDeclaration *const self = CHECK(L, 1);
-    std::string str(className);
-    str += std::string_view(" { ");
-    str += self->parentScript.empty() ? self->name : self->parentScript + "::" + self->name;
-    str += std::string_view(" }");
-    lua_pushstring(L, str.c_str());
-    return 1;
-}
-
-int
-FunctionDeclaration::setName(lua_State *L) noexcept
-{
-    FunctionDeclaration::CHECK(L, 1)->name = CHECK_STRING_VIEW(L, 2);
-    LUA_RETURN_NOTHING(L);
-}
-
-int
-FunctionDeclaration::setImportFromScript(lua_State *L) noexcept
-{
-    FunctionDeclaration::CHECK(L, 1)->parentScript = CHECK_STRING_VIEW(L, 2);
-    LUA_RETURN_NOTHING(L);
-}
diff --git a/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7030865fadffd8f6d7c8a896f6f31d937ce05f47
--- /dev/null
+++ b/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc
@@ -0,0 +1,50 @@
+#include "../CRTPLuaScriptObject.hh"
+#include "lua.hpp"
+#include <QFileInfo>
+#include <QString>
+
+using namespace Vivy::Script;
+
+int
+OptionDeclaration::TOSTRING(lua_State *const L) noexcept
+{
+    OptionDeclaration *const self = CHECK(L, 1);
+    std::string str(className);
+    str += std::string_view(" { ");
+
+    for (auto const &[name, opt] : self->options) {
+        str += "\"" + name + "\" -> ";
+        str += opt.getSignature();
+        str += ", ";
+    }
+
+    str.resize(str.size() - 2); // Delete the leading ", "
+    str += std::string_view(" }");
+    lua_pushstring(L, str.c_str());
+    return 1;
+}
+
+int
+OptionDeclaration::addOptionContent(lua_State *const L) noexcept
+{
+    OptionDeclaration *const self = CHECK(L, 1);
+    luaL_checktype(L, 2, LUA_TSTRING);
+
+    const std::string_view optName = CHECK_STRING_VIEW(L, 2);
+    const int valueType            = lua_type(L, 3);
+
+    // XXX: Add support for other types!
+    if (valueType == LUA_TNUMBER)
+        self->options.emplace(optName, lua_tonumber(L, 3));
+
+    else if (valueType == LUA_TSTRING)
+        self->options.emplace(optName, CHECK_STRING_VIEW(L, 3));
+
+    else if (valueType == LUA_TBOOLEAN)
+        self->options.emplace(optName, static_cast<bool>(lua_toboolean(L, 3)));
+
+    else
+        luaL_error(L, "Unsupported type %s", luaL_typename(L, 3));
+
+    LUA_RETURN_NOTHING(L);
+}