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 Promise
s 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-rfr
is 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 foo
are 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.