The purpose of this style guide is not only to keep the code style, file naming, and structure consistent, but also to make it easier to search through the source for the code you are looking for.
The rules may not be anyone's preference, but it is better to have a consistent style rather than having a preferred style.
Note
While not all legacy code follows these rules yet, new code should follow them.
All javascript code should follow the rules specified in the
.eslintrc file.
To make it easier, install ESLint support for your IDE
of choice.
All text files (.js, .md, .json, etc.) should follow the rules specified in the
.editorconfig file.
To make it easier, install EditorConfig support for
your IDE of choice.
- Use
const
for constants declared in a file's global scope. - Use
let
for variables declared inside functions, or methods, even if the intial value will never be changed.
// Const used to declare a static LocaleString outside of the class.
const txtHelloWorld = l10n.l('example.helloWorld', "Hello, world!");
// Variable declared inside a method or function.
let sum = 0;
- Use quote (
"Hello"
) for strings that will be rendered. - Use apostrophe (
'charId'
) for strings that will not be rendered, such as fields, properties, or ID keys. - Use backticks (
`Joe "Dog" Smith`
) for strings that contain quotes, or multiple lines.
// Quotes used for an error text to be rendered as an error message.
throw new Error("Invalid parameter");
// Apostrophes used for the 'change' event key.
model.on('change', callback);
// Backticks used for text spanning multiple lines.
const helpText =
`<p>Set an area attribute.</p>
<code class="param">Area</code> is the name of an owned area to set.</p>`;
- For long scoped variables and properties, use more explainatory names.
- For short scoped variables, single letter names are fine.
// Long name used for a class wide property
this.toolCollection = new Collection();
// Single letter name used for an index variable in a small loop
for (let i = 0; i < tools.length; i++) {
callback(i, tools[i]);
}
- Variables should use
camelCase
. - Abbreviations, acronyms, or initialisms, should be treated as words and use camelCase as well.
// Use of camelCase for a variable.
let ownedChars = this.module.player.getChars();
// Abbreviation of ID using lower case.
let id = 'example';
// Abbreviation of ID treated as a word.
let charId = getCharId();
- Methods should use the same rules at variable naming.
- Internal methods only to be used by the class (private/protected) should be
prefixed with an underscore (
_
).
// Underscore prefixing a private method.
_listen(on) { /* ... */ }
Properties should use the same rules at variable naming.
- Class names should use
PascalCase
. - Abbreviations, acronyms, or initialisms, should be treated as words and use PascalCase as well.
// Use of PascalCase for a class name.
class Api { /* ... */ }
// The acronym, UI, is treated as a word.
class NoUiSlider { /* ... */ }
- Files exporting a single default class should have the same name as the class,
with a
.js
extension. - Files exporting a single default function should have the same name as the
function, with a
.js
extension. - Files containing SCSS should have a camelCase name with a
.scss
extension.
Example file | Description |
---|---|
NoUiSlider.js |
File for the NoUiSlider class. |
formatDate.js |
File for the formatDate function. |
areaMap.scss |
File for the areaMap module's scss styles |
- Module name MUST be unique. Two modules cannot have the same name.
- Module name should use
camelCase
. - Abbreviations, acronyms, or initialisms, should be treated as words and use camelCase as well.
- Modules should have similar naming style as existing modules:
- Modules rendering a dialog (modal) should being with
dialog
. - Modules rendering a panel page should being with
page
. - Modules adding help categories should begin with
help
. - Modules adding a command should have the same name as the command, if it is available.
- Modules adding a command should be suffixed with
Cmd
if the module name is in conflict with another module's name.
- Modules rendering a dialog (modal) should being with
Example module name | Description |
---|---|
dialogCreateChar |
Module opening the Create Character dialog. |
pageChar |
Module rendering the Char Info page. |
helpRules |
Module adding the Rules help category. |
stopFollow |
Module adding the stop follow command. |
muteCmd |
Module adding the mute command, but is in naming conflicts with the mute module. |
Modules MUST be put in a subfolder under the folder matching the bundle it belongs to:
Module bundle folder | Bundle description |
---|---|
modules/init/ |
Modules loaded initially to allow login. |
modules/main/ |
Modules loading for all users. |
modules/assistant/ |
Modules loading for builders, moderators, admins, and overseers. |
modules/builder/ |
Modules loading for builders, admins, and overseers. |
modules/moderator/ |
Modules loading for moderators, admins, and overseers. |
modules/helper/ |
Modules loading for helpers, admins, and overseers. |
modules/admin/ |
Modules loading for admins and overseers. |
modules/pioneer/ |
Modules loading for pioneers and overseers. |
modules/overseer/ |
Modules loading for overseers. |
- Module folder MUST have the same name as the module.
- Module file MUST be the module name in
PascalCase
with the.js
extension. - All files belonging to the module should be in the same folder as the module file.
- All files belonging to the module should be prefixed with, or have the same name as the module, following the file naming rules.
- Module classes should only contain one class per file, named accordingly.
- Module folders should be grouped with similar modules (eg. dialogs, commands, pages, etc.)
- Main component of a module (if applicable) should have the same name as the
module, but with
Component
suffixed to the name. - Module style file should have the module name with the
.scss
extension. - Modules should only have one style file, and it should be imported from the module file.
Example file | Description |
---|---|
api/Api.js |
Module file for the api modul inside a folder with the module's name. |
main/dialogs/dialogCreateChar/DialogCreateChar.js |
Module is grouped with other dialogs in the dialogs/ folder. |
moderator/moderatorCommands/suspend/Suspend.js |
Module for the suspend command is put under the modules/moderator folder. |
init/login/LoginComponent.js |
Main component of the login module has Component added to the file name. |
init/login/login.scss |
Style file for all components in the login module. |
- Sass is used for all module styles.
- HTML classes are used to style components.
- HTML class names should be all lowercase.
- HTML class names should be prefixed with the module name in lowercase (see above).
- HTML class names for subcomponents (excluding the main component) should be
added to the module name separated with a single dash (
-
). It should match the file name. - HTML class names for internal elements of a component should be added to the
name separated with a double dash (
--
). - Module components should NOT use class names belonging to other modules.
Example class name | Description |
---|---|
login |
HTML class name for the LoginComponent.js component. It has no suffix as it is the main component of the login module. |
login-register |
HTML class name for the LoginRegister.js component which is a subcomponent to the login module. |
login-register--agree |
HTML class name for an internal element of the LoginRegister.js component. |
dialogcreatechar--disclaimer |
HTML class name for an internal element of the DialogCreateChar.js module component. |
Tip
Because of the HTML class naming rules, it is easy to inspect the DOM of the web application to see which module renders a certain element, using only class names as reference
For code documentation, JSDoc3 syntax is used.
- Module classes should have JSDoc comments describing what the module does.
- Module class constructor does NOT require any comment.
- Public module methods (methods not prefixed with
_
) should have JSDoc comments unless their use is obvious.
/**
* CharPing periodically sends a ping for all controlled characters
* to ensure they are not released out of inactivity.
*/
class CharPing { /* ... */ }
- Classes should have JSDoc comments describing what the class does.
- Class constructur and public methods (methods not prefixed with
_
) should have JSDoc comments unless their use is obvious.
- Exported helper functions under
src/common/utils
should have JSDoc comments.
To prepare for future multi-language support, all texts in the source code meant
to be rendered in the UI should be wrapped using the l10n
package:
- LocaleString key should be prefixed with the name of the module, followed by a dot.
- LocaleString key should be suffixed with a short explainatory name following the variable naming rules.
// The "Wake up" LocaleString defined in the pageCharSelect module.
l10n.l('pageCharSelect.wakeUp', "Wake up")
When applicable, follow the existing conventions and patterns. The sections below describes the most common ones.
- Always store the app reference passed to the module constructor in
this.app
.
constructor(app, params) {
this.app = app;
/* ... */
}
- Use
this._init
as callback method when requiring dependencies. - Put
this._init
method directly below the constructor. - Put dependencies on separate lines.
- Store module dependencies in
this.module
(no plural s). - Add
self
as a reference tothis
.
constructor(app, params) {
this.app = app;
/* ... */
this.app.require([
'api', // Dependencies on separate lines
], this._init.bind(this));
}
_init(module) {
this.module = Object.assign({ self: this }, module);
/* ... */
}
If a module ("host module") has a component or other class that other modules ("client modules") may create instances of, the following rules apply:
- Classes should only be exposed through modules if it has dependencies of other modules.
- Classes without dependencies of other modules should instead be put as a
shared class under
common/components/
orcommon/classes
. - Class instances should be created through a public module method:
- The method should be prefixed with
new
followed by the name of the class, but without the module name.
- The method should be prefixed with
- Module dependencies should by provided by the host module.
- Modules should NOT directly import class files of other modules.
// In the pageArea module, PageArea.js, a method prefixed with
// new is used to create an instance of PageAreaBadge.
newBadge(ctrl, area, opt) {
return new PageAreaBadge(this.module, ctrl, area, opt);
}
When creating "hooks" which allows other modules to register/unregister themselves from the module, the following rules apply:
- If multiple modules can hook up, use two methods prefixed
add
andremove
to allow registering/unregistering.- The
add
method should take an object with at least the properties:id
- ID string identifying the item being added.sortOrder
- Numeric sort order value. Only needed if order matters.
- The
remove
method should take the ID string value as argument. - Other modules adding themselves should call remove in their
dispose
method. - Optionally, a method prefixed
get
should return a collection of registered items.
- The
- If a single module can hook up, use two methods prefixed
set
andget
to allow registering, and checking what is currently registered.
Example from PageCharSettings.js
/**
* Gets a collection of tools.
* @returns {Collection} Collection of tools.
*/
getTools() {
return this.tools;
}
/**
* Registers a settings component tool.
* @param {object} tool Tool object
* @param {string} tool.id Tool ID.
* @param {number} tool.sortOrder Sort order.
* @param {function} tool.componentFactory Tool component factory: function(char, charSettings, state) -> Component
* @param {string} [tool.type] Target type. May be 'preference' or 'section'. Defaults to 'preference';
* @param {number} [tool.className] Class to give to the list item container.
* @returns {this}
*/
addTool(tool) {
if (this.tools.get(tool.id)) {
throw new Error("Tool ID already registered: ", tool.id);
}
this.tools.add(tool);
return this;
}
/**
* Unregisters a previously registered tool.
* @param {string} toolId Tool ID.
* @returns {this}
*/
removeTool(toolId) {
this.tools.remove(toolId);
return this;
}
Example from Screen.js
/**
* Get current component.
* @returns {Component} Current component.
*/
getComponent() {
return this.component;
}
/**
* Set component.
* @param {Component?} component Component to set.
* @returns {this}
*/
setComponent(component) {
this.component = component || null;
this.fader.setComponent(component);
return this;
}