Guidelines:Modules: Difference between revisions

Jump to navigation Jump to search
no edit summary
No edit summary
(25 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{ZW Nav|Guidelines}}
{{ZW Nav|Guidelines}}
{{Big|Overview|3}}<br>
{{Big|Overview|3}}<br/>


'''Modules''' are {{Wp|Lua (programming language)|Lua}} scripts for making [[Help:Templates|templates]] using a full-fledged programming language. You can ask questions about Zelda Wiki modules in the <code>#modules</code> [{{Discord}} Discord] channel.
'''Modules''' are {{Wp|Lua (programming language)|Lua}} scripts for making [[Help:Templates|templates]] using a full-fledged programming language. You can ask questions about Zelda Wiki modules in {{Discord|channel= wiki-tech}}.


==Templates==
==Templates==
Line 11: Line 11:
In order to contribute to modules, you'll need to learn basic programming in Lua. You'll also need to know the specifics of Lua scripting on MediaWiki.  
In order to contribute to modules, you'll need to learn basic programming in Lua. You'll also need to know the specifics of Lua scripting on MediaWiki.  


===Lua===
===Lua for Beginners===
====Beginners====
There are many excellent online resources designed to teach programming fundamentals... But not in Lua.<!-- If there is an excellent one I've yet to see it. The ones I've seen don't compare to ones offered in more prominent languages. --> Fortunately, Lua is a relatively small language. Jump in and try to learn through the [[Guidelines:Modules/Exercises|Exercises]].  
There are many excellent online resources designed to teach programming fundamentals... But none of them are in Lua. Fortunately, Lua is a small and simple language. Jump in and try to learn through the [[Guidelines:Modules/Exercises|Exercises]].  


If you find you need more guided lessons, try Codeacademy's [https://www.codecademy.com/learn/learn-python Python 2] course, or Khan Academy's [https://www.khanacademy.org/computing/computer-programming/programming Intro to JavaScript]. Python is closer to Lua than JavaScript, but Khan Academy's course has the benefit of video talk-throughs. In any case the fundamental language concepts are the same.
If you would prefer more guided lessons, try Codeacademy's [https://www.codecademy.com/learn/learn-python Python 2 course] or Khan Academy's [https://www.khanacademy.org/computing/computer-programming/programming Intro to JavaScript]. Lua is closer to Python than to JavaScript, but Khan Academy's course has the benefit of video talk-throughs. Learning JavaScript would allow you to build [https://community.fandom.com/wiki/Help:Gadgets Gadgets] or write [https://community.fandom.com/wiki/Help:Personal_CSS_and_JS personal modifications] to the wiki's interface.


====Developers====
If you would prefer a book, try [https://www.golang-book.com/books/intro An Introduction to Programming in Go] by Caleb Doxsey. Due to the nature of Go, this book teaches more general-purpose computing fundamentals than what you would need for writing wiki modules. Were you to pursue programming beyond the wiki, those concepts would likely serve you well.
 
===Lua for Developers===
If you already have some programming experience in other languages, read [http://tylerneylon.com/a/learn-lua/ Learn Lua in 15 minutes].
If you already have some programming experience in other languages, read [http://tylerneylon.com/a/learn-lua/ Learn Lua in 15 minutes].


Line 81: Line 82:


====<code>#invoke</code>====
====<code>#invoke</code>====
The <code>#invoke</code> {{Mediawiki|Help:Extension:ParserFunctions|parser function}} is what bridges the gap between templates and module scripts. For example, the {{Mediawiki|Transclusion|transcluded}} content of [[Template:Figurine]] is as follows:
The <code>#invoke</code> {{Mediawiki|Help:Extension:ParserFunctions|parser function}} is what bridges the gap between templates and module scripts. For example, the {{Mediawiki|Transclusion|transcluded}} content of [[Template:Color]] is as follows:
<pre>
<pre>
{{#invoke:Figurine|Main}}
{{#invoke:Color|Main}}
</pre>
</pre>


This invokes [[Module:Figurine]], which could look something like:
This invokes [[Module:Color]], which looks something like:


{{Hide
|visible= true
|header= Module:Color
|content=
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
local p = {}
local p = {}
local h = {}
 
local utilsGame = require("Module:UtilsGame")
...
...


function p.Main(frame)
function p.Main(frame)
     local args = frame:getParent().args
     local args = frame:getParent().args
     local result = h.getFigurine(args[1], args[2])
     return p.color(args[1], args[2])
    return result
end
end


function h.getFigurine(game, name)
function p.color(color, text)
...
    ...
end
end


return p
return p
</syntaxhighlight>
</syntaxhighlight>
}}
A page can invoke any function that is a field on the module's '''export table'''. The export table is the table object returned by the module, which is named <code>p</code> by convention (short for "package").
In the above example, <kbd>Main</kbd> and <kbd>color</kbd> are the functions in the export table. Only <kbd>Main</kbd> can be used by [[Template:Color]] via <code>#invoke</code>. The <kbd>color</kbd> function can be used by other modules (see [[#require|<kbd>require</kbd>]] below).


A page can invoke any function that is a field on the module's '''export table'''. The export table is the <code>table</code> object returned by the module, which is always named <code>p</code> by convention. In this case, <code>Main</code> is the only function in the export table.
By convention, {{SITENAME}} uses <kbd>UpperCamelCase</kbd> to indicate functions meant for <code>#invoke</code>, and <kbd>lowerCamelCase</kbd> for all other functions.


====<code>args</code>====
====<code>args</code>====
Functions called via <code>#invoke</code> are passed a {{Mediawiki|Extension:Scribunto/Lua reference manual#Frame object|Frame object}}. <code>frame.args</code> is a <code>table</code> of the arguments to <code>#invoke</code>—there are none in the above example. However, <code>frame:getParent().args</code> represents the ''template'' arguments. For example, if a page has <code><nowiki>{{Figurine|TMC|Minish Ezlo}}</nowiki></code>, then <code>frame:getParent().args[1]</code> evaluates to the string <code>TMC</code> in that invocation.
Functions called via <code>#invoke</code> are passed a {{Mediawiki|Extension:Scribunto/Lua reference manual#Frame object|Frame object}}.
*<code>frame.args</code> is a table of the arguments to <code>#invoke</code>. (There are none in the above example.)
*<code>frame:getParent().args</code> is a table of the arguments to the ''template''.
**Example: If a page has <code><nowiki>{{Color|TWWHD Vermilion|merchant's oath}}</nowiki></code>, then <code>frame:getParent().args[1]</code> evaluates to the string <code>TWWHD Vermilion</code> for that invocation.


The <code>#</code> operator and most other table functions don't work on <code>frame.args</code>.
The <code>#</code> operator and most other table functions don't work on <code>frame.args</code>.


====<code>require</code>====
====<code>require</code>====
A module can import another module and use its exported functions. This is done with the <code>require</code> function, as shown in the above example with [[Module:UtilsGame]].
A module can import another module and use its exported functions. This is done with the <code>require</code> function.
 
For example, [[Module:Cite]] imports [[Module:Color]] in order to use the <kbd>color</kbd> function to color quotes of characters whose dialogue text is a special color in-game.
 
{{Hide
|visible= true
|header= Module:Cite
|content=
<syntaxhighlight lang="lua">
local p = {}
 
local Color = ("Module:Color")
 
...
 
function p.Main(frame)
    ...
    Color.color(color, quote)
end
 
return p
</syntaxhighlight>
}}


====<code>mw</code>====
====<code>mw</code>====
Line 134: Line 167:
|}
|}


===Testing and Debugging===
===Debugging===
You should never be in a situation where you're blindly submitting code and hoping that it works. The [[Guidelines:Modules/Exercises#Getting Started|Geting Started exercise]] covers how to preview changes to a module. The following exercise covers logging. The [[gphelp:Extension:Scribunto#Debugging|Gamepedia Help Wiki]] covers the other two debugging tools: the debug console and and script errors.
:''See also [[gphelp:Extension:Scribunto#Debugging]]''
You should never be in a situation where you're blindly submitting code and hoping that it works. The first two [[Guidelines:Modules/Exercises|exercises]] of these guidelines cover how to debug with previewing, logging, and the debug console.


A good page to preview is the module's documentation page when it has use cases (as [[Module:UtilsMarkup/Documentation]] does, for example), or the corresponding template documentation when it has usage examples (e.g. [[Template:Term/Documentation]]). An effective way to develop modules is to make the [[Module:Documentation/Documentation|documentation examples]] before the module itself, then using them as a preview target while making the actual module. (This is in fact a form of {{Wp|test-driven development}}.)
Always ensure that your code does not produce [[:Category:Pages with script errors|script errors]]. Please fix or revert any changes that do. Keep an eye on [[:Category:Articles Using Invalid Arguments in Template Calls]] as well.
 
===Testing and Documentation===
{{Main|Module:Documentation}}
When coding a module, the best page to preview is often the corresponding template's documentation. If it exists, the page should have [[Template:Examples|usage examples]] for every available feature. In fact, an effective way to write modules is actually to write the documentation examples before the code itself. When you're writing the actual code, previewing that documentation page will tell you if your code is working—and will give you material to debug with if it isn't. The practice of writing test cases before the code is called {{Wp|test-driven development}}.
 
You should also write [[Module:Documentation#Modules|module documentation]] for any Lua function meant to be used by other modules. Module documentation can double as automated unit tests via the [[Module:Documentation#Tests|<code>expect</code>]] property. If the output of the function does not equal what is expected, the page is added to [[:Category:Modules with failing tests]].


==Utility Modules==
==Utility Modules==
A utility module is a library-type module meant to be used by other modules, rather than being invoked by a template. Most template-facing modules use at least one of these:
A utility module is a library-type module meant to be used by other modules, rather than being invoked by a template. Most template-facing modules use at least one of these:
* [[Module:UtilsGame]]
 
* [[Module:UtilsArg]]
* [[Module:UtilsMarkup]]
* [[Module:UtilsMarkup]]
* [[Module:UtilsString]]
* [[Module:UtilsTable]]
* [[Module:UtilsTable]]


A list of utility modules is available at [[:Category:Utility Modules]]. Leverage utility modules as much as possible so that the wiki's codebase stays {{Wp|Don't repeat yourself|DRY}}.
A list of utility modules is available at [[:Category:Utility Modules]]. Leverage utility modules as much as possible so that the wiki's codebase stays {{Wp|Don't repeat yourself|DRY}}.
===Higher-Order Functions===
In utility modules such as [[Module:UtilsTable]], you'll often see functions like these:
<syntaxhighlight lang="lua">
utilsTable.isEqual({})({})
> true
</syntaxhighlight>
You might've expected the usual function syntax <code>utilsTable.isEqual({}, {})</code> instead. The above is an example of a '''higher-order function'''—a function that returns a function. The function call above is shorthand for:
<syntaxhighlight lang="lua">
local isEmpty = utilsTable.isEqual({})
isEmpty({})
> true
</syntaxhighlight>
Functions are written this way for increased reusability. They are often composed with the other type of higher-order function—one that accepts a function as an argument:
<syntaxhighlight lang="lua">
local isEmpty = utilsTable.isEqual({})
local notEmpty = utilsFunction.negate(isEmpty)
local magicWords = utilsTable.filter(notEmpty)({ {}, {}, {"Kooloo"}, {"Limpah"} })
utilsTable.flatten(magicWords)
> { "Kooloo", "Limpah" }
</syntaxhighlight>


==Exercises==
==Exercises==
Line 177: Line 196:
Once you are able to produce working code, make sure it adheres to Zelda Wiki's coding standards.
Once you are able to produce working code, make sure it adheres to Zelda Wiki's coding standards.


It is particularly important to use a consistent naming pattern, as plain text [[Special:Search|searching]] is the only way to observe function usage.<!--
It is particularly important to use a consistent naming pattern, as plain text [[Special:Search|searching]] is the only way to observe function usage.
-->{{#vardefine:Don't|{{Exp|Don't|[[File:TFH Red Link desperate.png|48px|link=]]}}}}<!--
 
-->{{#vardefine:Do|{{Exp|Do|[[File:TFH Green Link ok.png|36px|link=]]}}}}
===Importing===
===Importing===
{| class="wikitable"
{| class="wikitable"
!colspan=2| Use lower <code>camelCase</code> for imported utility modules
!colspan=2| Use lower <code>camelCase</code> for imported utility modules
|-
|-
! {{#var:Don't}}
! {{Bad|tooltip= Don't}}
|<syntaxhighlight lang="lua">
|<syntaxhighlight lang="lua">
local UtilsGame = require("Module:UtilsGame")
local UtilsString = require("Module:UtilsString")
</syntaxhighlight>
</syntaxhighlight>
|-
|-
! {{#var:Do}}
! {{Good|tooltip= Do}}
|
|
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
local utilsGame = require("Module:UtilsGame")
local utilsString = require("Module:UtilsString")
</syntaxhighlight>  
</syntaxhighlight>  
|-
|-
Line 198: Line 216:
!colspan=2| Avoid importing [[:Category:Submodules|submodules]] unless absolutely necessary
!colspan=2| Avoid importing [[:Category:Submodules|submodules]] unless absolutely necessary
|-
|-
! {{#var:Don't}}
! {{Bad|tooltip= Don't}}
|
|
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 204: Line 222:
</syntaxhighlight>
</syntaxhighlight>
|-
|-
! {{#var:Do}}
! {{Good|tooltip= Do}}
|
|
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 213: Line 231:
!colspan=2|Don't index the result of the <code>require</code> expression.
!colspan=2|Don't index the result of the <code>require</code> expression.
|-
|-
! {{#var:Don't}}
! {{Bad|tooltip= Don't}}
|
|
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 219: Line 237:
</syntaxhighlight>
</syntaxhighlight>
|-
|-
! {{#var:Do}}
! {{Good|tooltip= Do}}
| If you must assign the function separately:
| If you must assign the function separately:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 232: Line 250:
!colspan=2|Default to double quotes
!colspan=2|Default to double quotes
|-
|-
! {{#var:Do}}
! {{Good|tooltip= Do}}
|
|
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 241: Line 259:
</syntaxhighlight>
</syntaxhighlight>
|-
|-
! {{#var:Don't}}
! {{Bad|tooltip= Don't}}
|  
|  
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 247: Line 265:
</syntaxhighlight>
</syntaxhighlight>
|}
|}
===Errors===
{| class="wikitable"
!colspan=2|Template calls must not throw script errors on purpose. Use warnings and error categories instead.
|-
! {{Good|tooltip= Do}}
<syntaxhighlight lang="lua">
function(foo)
  if not foo then
    mw.addWarning("'foo' is required.")
    return "[[Category:Pages with Invalid Arguments]]"
  end
  local bar = foo.bar
  ...
end
</syntaxhighlight>
[[Module:UtilsArg]] can help with this.
|-
! {{Bad|tooltip= Don't}}
|
<syntaxhighlight lang="lua">
function(foo)
  local bar = foo.bar -- throws an error if foo is nil
  ...
end
</syntaxhighlight>
|-
! {{Bad|tooltip= Don't}}
|
<syntaxhighlight lang="lua">
function(foo)
  if not foo then
    error("foo cannot be nil")
  end
  local bar = foo.bar
  ...
end
</syntaxhighlight>
|}
==Performance Optimization==
High-usage modules may require additional effort for them to work well at scale. "High usage" can mean one of two things:
'''1. Modules used many times per page'''
Examples: [[Module:Cite|Cite]], [[Module:Color|Color]], [[Module:Term|Term]]. Some pages invoke these modules 300+ times.
The Lua processing time quickly adds up in these cases, so the modules must be as fast as possible to avoid slowing down wiki page processing. Total Lua processing time on a page must be less than 7 seconds⁠⁠—any Lua code running after that is aborted and manifests as a [[:Category:Pages with script errors|script error]]. Ideally, Lua processing time should be less than 1 second.
What you can do:
* Use the [[#Profiling|Profiler]] to find out what's slowing down a module.
* Follow [[Module:Documentation#Performance Optimization]].
* Follow https://dev.fandom.com/wiki/Lua_templating/Optimisation
'''2. Modules used on many pages'''
Examples: [[Module:FileInfo|FileInfo]], [[Module:Franchise|Franchise]], [[Module:UtilsArg|UtilsArg]].
Every time such a module is updated, every page that uses it (directly or indirectly) must be updated. This can set off a large {{Mediawiki|Manual:Job queue|job queue}}. When there are many active jobs in the queue, any changes to other templates (high-usage or no) may take several hours to propagate to all the pages that use them. The same goes for [[Special:ReplaceText]] operations, which are also sent to the back of the job queue.
What you can do:
* Put high-usage functions in separate modules.
* Minimize the number of imports in high-usage modules.
A case study: [[Module:File]] used to contain the #invoke functions for [[Template:FileInfo]] and [[Template:File Redirect]] as well as utility functions for image thumbnails. As a result, the module was linked on nearly every page on the wiki (~50,000 for all articles and files). Changing the code for file redirects would create a job queue for ''all'' these pages even though file redirects only number a few hundred. The solution to this problem was to create three separate modules: [[Module:File]] for rendering image thumbnails, [[Module:FileInfo]] for file descriptions, and [[Module:FileInfo/File Redirect]] for file redirects. This way, changes to any module function would only affect the pages that actually use them.
===Profiling===
Our MediaWiki installation comes with a Lua Profiler which indicates how much time and memory Lua takes up when loading a page on the wiki. It will also show the top ten Lua functions which contribute the most time usage.
To generate a profiler report, preview a page on the wiki. The report is located at the bottom of the page under the editing area, in the '''Parser profiling data:''' section which may be collapsed by default. It looks something like this:
{| class="wikitable"
! Lua time usage
|colspan=2| 1.253/7.000 seconds
|-
! Lua memory usage
|colspan=2| 15,186,425/52,428,800 bytes
|-
! colspan=3 | Lua Profile
|-
|Scribunto_LuaSandboxCallback::query || 200 ms || 14.7%
|-
|recursiveClone <mwInit.lua:41> || 200 ms || 14.7%
|-
|Scribunto_LuaSandboxCallback::preprocess || 140 ms || 10.3%
|-
|Scribunto_LuaSandboxCallback::get || 120 ms || 8.8%
|-
|? || 100 ms || 7.4%
|-
|init <Module:UtilsTable/Inspect> || 60 ms || 4.4%
|-
|init <Module:UtilsString> || 60 ms || 4.4%
|-
|Scribunto_LuaSandboxCallback::newTitle || 60 ms || 4.4%
|-
|Scribunto_LuaSandboxCallback::callParserFunction || 60 ms || 4.4%
|-
|init <Module:UtilsArg> || 40 ms || 2.9%
|-
|[others] || 320 ms || 23.5%
|}
The Lua Profile section only appears when total Lua time usage on the page is above 1000ms (1 second). <!-- I think. -->


{{Guidelines Nav}}
{{Guidelines Nav}}

Navigation menu