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

  1. 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

  1. 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.

  1. 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" package node-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
    ...
}

  1. 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.

  1. 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!"))
  1. 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.