Development Guidelines - UoGSOCIS/website GitHub Wiki
Overview
As this project has changed hands multiple times, to ensure that this project is always in a maintainable state, be sure to follow the guidelines outlined below.
Contributing
- Use Feature Branches
The master is a protected branch only meant to hold well-testing,
working code from our iterations.
It should only ever contain code that is in a fully-working state.
When adding a new feature, checkout the master branch, then
create a new branch as follows:
git checkout -b "$ticketNum-short-description"
e.g.
git checkout -b 999-calculate-meaning-of-life
Then when you've made changes, push the branch to the GitLab:
git push -u origin "$ticketNum-short-description"
When your feature is complete (or ready for preliminary review), open a
new Pull Request into the master branch.
Code Style
- Lint your code
Be sure to familiarize yourself with the eslint
tool. Install it with
npm -g install eslint eslint-config
This project follows a style guide which is defined in the
.eslintrc.js file at the root of the project.
Code which has errors when linted by eslint is not permitted, and
warnings are to be taken seriously. If eslint detects warnings on
development branches, it is not to be merged into master.
Be sure to run npm run lint to automatically check your code
before you push to git. This is part of the CI Pipeline.
- ES6 features
Use ES6 features like let and const to prevent variable scope
hoisting.
Return Promises using fat arrow notation wherever a callback would
normally be used. For example:
// DO NOT write functions that take callbacks
function asyncFunc(inputs, callback) {
processData(inputs, function(err, data) {
if (err) return callback(new Error(), null);
else return callback(null, data);
}
}
// DO write functions that return promises; this allows
// functionality to be chained together without nesting later
// (even if the Promise function itself must call a function that
// uses callbacks)
function asyncFunc(inputs) {
// this definition of asyncFunc returns a promise
processData(inputs, (err, data) => {
// this call to processData uses the fat-arrow notation for
// the expected callback; the promise is resolved in the body
if (err) return Promise.reject(err);
return Promise.resolve(data);
});
}
Do not use ES6 import and export statements. require and
module.exports statements are preferred for simplification of module
paths as described below.
-
In large Node.js projects,
require()statements can get messy as one descends deeper into the project directory structure. For simplicity and increased maintainability during refactoring processes, the "require from root" packagenode-rfris used to manage require paths for custom modules. -
This package is required at the top-level of every module as
var source = require(rfr);Subsequent use of custom modules is
const bar = source(path/from/project_root/foo);This is for semantic reasons, since it's natural to "source" a package, but it not natural to "rfr" a package.
-
Modules installed with
npm install fooare still required as per normal. Modules that require files from the same directory may require them as per normal.
Example of basic module:
/**
* @module mymodule
*/
"use strict";
const source = require("rfr");
const path = require("path");
const logger = source("logger");
const other = require("./otherfile.js");
function hello() {
console.log("Hello world!");
}
...
module.exports = {
hello: hello,
notInThisFile: other.func, // promote visibility to this module
...
}
- Write proper modules
These have their own (possibly nested) sub-directories in the project
and have an index.js, even if the module is only one file.
For example:
foo/ // foo always imported as `source("foo")`
| \ index.js
|
+-- bar/ // bar always imported as `source("foo/bar")`
\ index.js // index.js uses private bar.js as `require(./bar.js)`
\ bar.js
For modules with multiple source files, the index.js file should
not introduce very much new functionality (unless it defines classes
that are integral to a model, as in models/user); rather it should
just export specific functionality from files within the same module.
- Logging
Do not use console.log()!
This project uses a leveled logger with persistent log files.
Instead be sure to source const logger = source("logger"); and
use one of the following functions:
logger.debug("some debugging info that won't show up in prod")logger.info("Hello!")logger.warn("Something dangerous but non-critical")logger.error(new Error("Critical error!"))
- Document your code!
All modules (even private sub-modules) must be documented using
jsdoc commments.
See Code Documentation for more information.
Miscellany
Path management
Aside from the distinction between source and require made above,
resource path management is important for web applications. If you
need to do anything with static resource paths, you can refer to them
with respect to the project's top-level directory by the following:
const source = require("rfr");
const path = require("path");
const config = source("config");
let mypath = path.join(config.__projectdir, "/path/to/my/thing");
Where config.__projectdir is the directory that contains config.js
and node_modules. In this way, one can reliably refer to resources
located anywhere in the project in a well-defined and consistent
manner.