Home Manual Reference Source Test Demo

NOTE: Any manual changes to Github Wiki will be overwritten by push (PR merged) to master branch

These files should updated whenever we merge to master, and be in sync between:

This project uses a Github Action to accomplish this. The action is executed on merge to the master branch and defined inside of the ./.github/workflows/ directory(github) (along with all other github actions) that creates a commit to update the docs directory on github and then calls a script in the scripts/actions directory(github).

The script retrieves the following markdown files (defined in ./.esdoc.json) and copies them to a temporary directory, then uses the wiki-page-creator-action to update the wiki.

Table of Contents

  • Setup - dependencies and how to install and start development (eventually production?)
  • Entry - How Javascript Bundles are defined and files that can be included
  • HTML - How HTML files are created and the processes we use to ensure quality
  • CSS - CSS Normalization, Processing and Frameworks
  • Images - Including images and SVG icons from free library
  • Helpers - Javascript Helper Classes and Code that is used in the project
  • Static HTML Components - HTML Components that are generated during the build process
  • Dynamic HTML Components - HTML/JS Components that are created with Handlebars dynamically during runtimes
  • Github Actions - Notes about Actions run on to ensure quality
  • Testing - How testing is configured and how to run tests
  • Custom Webpack Loaders - Explaining how we configure webpack loaders to generate the application

.

Setup

Requires Node 12+ to build. Please install this before attempting to build. The easiest way to install and manage node versions is by using the Node Version Manager. We define a .nvm file in the project to help reference this minimum node version.

Use nvm use 12 to switch node versions after installing a new Node version. Then you should install all project dependencies(github) with npm install -D. This should install Webpack and all the libraries/plugins used in this project.

If you have cloned the master branch, then the build should compile and all tests should pass once the dependencies have been downloaded and installed. To only compile the html and javascript, use npm run build, or you can run the tests with npm run test. To watch the files for changes and rebuild/retest the files on changes, you can use npm run start-watch. See the package.json(github) for all possibe npm commands.

Webpack

Webpack config file(github) is used to manage webpack build to compile the application.

This file:

  • Defines the Entrypoint javascript files to create bundles
  • Defines where the generated files are placed
  • Defines how to import other files
  • Sets up Modules/Plugins:
    • Babel and ESLint Loaders
    • Flow Integration with Webpack
    • Reference HTML Files (source and destinations) and javascript bundles to include on pages
    • PostCSS Loader for easy CSS files
    • Hot Module Replacement for rebuilding on file changes
    • Custom Script to run at end of webpack build that Lints generated HTML files
  • Sets up Development Server to Host HTML/JS/CSS Files for rapid development

Dev Server

During development (while using the npm run start-watch command), a Webpack Dev Server is instantiated to host the generated WebApp files. The Dev Server is manually instantiated in the webpack.config.js file because the scripts actually start Karma to watch and manage the test runs, which then runs the Webpack config file to start the build process and this Dev Server.

Documentation

The docs/ directory is created with npm run doc, this generates an esdoc webpage based on the modified template stored in docs/template from the README files in the repo. These docs are also synced with the Github wiki page via a Github Action (see Github Actions/Scripts page for more details.

The Dependency Graph is created with Madge and graphviz, you will need to install both in order to update the dependency graph. Didn't include these in the package.json dependencies because this is more than is needed to develop a working app or even write basic documentation. This is split out to a separate npm script: npm run doc-image so it can be run when someone actually installs the dependencies

Install madge with npm install -g madge and install graphviz with brew install graphviz or port install graphviz (for OSX). Then run npm run doc-image in the repo and add the created image from docs/image/dependency.png and commit to update the docsite.

Javascript Entry Points

The webpack.config.js(github) defines entry points for javascript bundles that can be created for each page, or can be optimized to bundle code that can be cached separately, loaded asynchronously or lazy loaded, after the page has loaded and rendered.

These bundles are separately included on each HTML page (defined with the HTMLWebpackPlugin in the Webpack config) and will execute in the order they are included on the webpage. The files in these bundles are separate modules and self-contained, so any variables will only be available within that module context. However, any items declared at the global level in a bundle (e.g.global.property = 'value';) will be available in any other bundle that is on that HTML page, as well..

Importing Files

The javascript file that is referenced as an entry point, can import other files and/or libraries using either CommonJS Module syntax (e.g. const package = require('module')) or using ECMA6(ES2015) syntax (e.g. import package from 'lib'). These files will then be included in the generated bundle for that entry point.

The file types available to import are defined in the rules section of the Webpack config:

  • Javascript (.js) Files
  • CSS (.css) Files
  • Images (.png/.svg/.jp(e)g/gif) Files

Entry Notes/Ideas

  • [ ] Fonts

HTML

HTML files structure the layout of the web application. The final HTML files are generated by webpack and output to the public/ directory when building with npm run build and define the pages that can be accessed via browser in the final application.

Webpack

The webpack.config.js file(github) defines the HTML pages that will be generated by the build process. For each instance of the HtmlWebpackPlugin in the plugins section, webpack will output a new HTML file using the template at the filename provided (inside of public/).

Page Header Meta Attributes

The <meta> webpage attributes are passed into the instance of the HtmlWebpackPlugin for each page defined in the webpack config file. These attributes could be dynamically set for each page if desired, or can be the same for each html page (as they are currently) and imported from a json file(github)

Templating

Static

Simple Static HTML Templating can be done with the underscore-lodash-loader. This loader allow HTML files to be imported during the compilation process using @require or @import in the HTML files, and also enables variables that can be replaced when the HTML is generated (e.g. <%= name %>) will be replaced if we use @import(<file_name>, { name: "Devlin Junker" }) with Devlin Junker.

Dynamic Javascript Components (Handlebars)

More complicated components (especially those that require javascript or looping/conditional statements should be written with handlebars in the components/ directory.

If the component should respond to user interactions and events, the component should have a .hbs file and an index.js file containing a function, that imports the .hbs file and compiles it with the parameters and handlers passed in.

Other, less complex components can be written purely in .hbs files with helpers and partials.

HTML Linting

During the build process, webpack is configured to lint the final HTML files after each successful build with htmlhint to spot any errors in the HTML structure. This will cause an error in the build process output. You can see the rules defined in the .htmlhintrc file (github).

These include:

  • Requiring Doctype
  • Disabling Inline CSS Styles on elements
  • Attribute naming conventions
  • Requiring some accessibility attributes

HTML Notes/Ideas

  • [x] Templating
  • [ ] Accessibility Linting
  • [ ] UI Framework?

CSS

CSS defines the look and feel of the application, giving style to the HTML elements on the page. This project uses a couple of tools during the build process to ensure quality CSS code with some of the most recent improvements to the CSS ecosystem.

The project can parse and import either .scss or .css files via javascript module imports, the .scss file ending is used so IDEs have an easier time with syntax highlighting.

Tailwind CSS Framework (Utility First)

Tailwind is included in this project to style the application. Tailwind is a composable, utility-first css framework. This is a shift from the "semantic" based css conventions where classes/styles are defined around a name that has meaning in the application domain. In this way of thinking, css classes define a consistent look and feel of the application. These utility classes are then applied to the elements in each html file to create a consistent style across the application that is not predefined.

Tailwind is built on top of normalize.css to standardize the look and feel of the application across multiple browsers and clear any default styles.

Tailwind's colors/padding/etc is customizable via the theme in the tailwind.config.js

Review these screencasts and other resources for more information. Visit the docs for configuration and notes on each rule as well as some example components

Mini CSS Extract Plugin

The webpack config file(github) imports the mini-css-extract-plugin and loader to extract all css styles that have been imported into the javascript bundles created by webpack. This enables the stylesheets to be referenced in the HTML file separately, so they can be linked at the top of the page and loaded first and also so they can be cached in the browser to speed up page load.

PostCSS

PostCSS is a javascript tool that parses and transforms the CSS files. The webpack config contains a reference to the postcss-loader so that css files can be generated during the build process from css files that are referenced in the codebase. The postcss.config.js file(github) defines the plugins that we use during the PostCSS parsing and generation process.

PreCSS and PostCSS-Preset-Env

PreCSS enables SASS like features in the css files including variables and nesting selectors generating the proper definition in the final CSS file. The PostCSS-Preset-Env plugin enables many new CSS features listed at the link provided.

Stylelint

Stylelint is used to validate the css file uses proper syntax. The rules for this project are defined in the .stylelintrc file (github). The rules extend the Standard Stylelint Config, and additional rules can be added to this config file.

See list of all rules: here
See list of suggested Additions: here

Autoprefixer

Autoprefixer is used to help keep the css files clean by automatically adding browser/vendor prefixes to css rules.

List Selectors

List Selectors is a plugin that we use to generate a json file containing the css selectors that are processed by PostCSS.

  • [ ] Show output selectors, not input
  • [ ] Map of selectors to css rules?

CSS Notes/Ideas

  • Guidelines: https://cssguidelin.es/
  • IDEA: During development (npm run start-watch) css is included in JS bundles for faster build
    • only extract during production to improve the look and feel of app loading
  • [ ] BEM Linting?
  • [ ] Styleguide
  • [ ] Framework Demos (Separate Repo?)
  • MVP (Product/Business) Style: https://andybrewer.github.io/mvp/
    • remember this for the future if need a splash screen for company/product/webapp

Images

All images for the web application should be stored in the img/ directory. An alias is configured in the webpack.config.js file (github) to enable requiring images with require("img/<path_in_img_dir") or referencing in .html or .hbs files src property.

Icons

This template includes Zondicons, a free SVG icon library with 270 icons. These can be included in the HTML template files with @svg(<img/<path_to_svg_relative_to_img>) and the svg file will be included inline so standard utility classes from Tailwind can be applied.

It is best to wrap the icons in a wrapper containing the icon class, as this will default to the icon being the size and color of the font.

Image Notes/Ideas

  • [ ] Icon page in docs that shows all icons and their paths for including in partials

Helpers

This project contains a helpers/ directory(github) that holds Javascript classes and methods that can be used across multiple projects and entry points with reusable methods and commonly used logic.

Helper Examples

Notes on the Example Helper class and methods in this project:

LocalStorage Helper

The localstorage helper (github) contains methods for saving/retrieving values with the browser localstorage API. Values saved with this will be stored on the clients browser and can be retreived when they return to the web application (until they clear their browser localstorage/data).

Relative Href Helper

The Relative Href helper(github) contains a small snippet of code that must be included in each javascript entry point. This code sets the <base> tag inside of the <head> element in the HTML page to tell relative links on the page the href that they should be relative to.

i.e. if <base href='http://test.com/'> exists at the top of the page, all relative links (e.g. <a href="example">...</a>) will direct to http://test.com/example (where example is any relative href value). This is compared to the regular relative link behavior where the link is relative to the current page (value in the browser url bar).

Handlebars Helpers

Handlebars is the simple HTML templating framework this project uses to create HTML components. Handlebars lets the project import it's own helpers in the webpack.config.js (github) that can add additional functionality and logic to the template partial files easily.

Default Value Helper

Helper that checks a variable to see if it contains a value and returns that value if it does, or else it returns a default value (2nd parameter).

e.g. will output '123' unless testVar contains a value, in which case it will return the value in testVar

// .hbs
{{default-val testVar '123'}}

let context = { }
// Output
123

let context = { testVar: 'abc' }
// Output after
abc

Eq Helper

Helper that compares to variables and checks if they are equal with ===. Returns true or false. This is best used inside of an expression in an #if block helper.

e.g. (will display Hello World! on page if context variable var === 'true')

// .hbs file
{{#if (eq var 'true')}} Hello World! {{/if}}

let context = { var: true };
// Output
Hello World!

let context = { var: false };
// Output

Helper Notes/Ideas

  • [ ] Configuration file
  • [ ] Performance Logger
  • [ ] API Helpers (Internal vs External?)

Static HTML Components

Some parts of the interface can be generated during the build process to reduce processing on the client side and speed up page generation if the assets are not dynamic in relation to the user. These components are generated using underscore-template-loader and can be included in the entry .html pages or .hbs files with @require(<path_to_static_file>, args) with optional arguments

Custom Macros

Custom macros can be created that execute some function during the build process. This project has example custom macros that include the build details and repository information in the output HTML pages. These macros are included in the webpack.config.js(github) configurations for the underscore-template-loader. These macros are given names in the webpack config file and then accessed using the @ symbol (e.g. @year()).

Static Component Examples

A couple of notes on static component examples included in this project:

Header

Contains links to the different part of the application, this is a good example of being dynamic (but not relative to the user) but still being able to be compiled during the build process.

Footer

Contains reference to the project details and build details for developers. This could also include:

  • contact information
  • donation links
  • other navigation component or site tree.

Static Partial Notes/Ideas

  • [ ] Move macros out of webpack.config.js into their own file (in src/static/ ?)

Dynamic HTML Components

Dynamic Components can be created with Handlebars partials and javascript files to add event handlers that toggle properties, trigger backend calls and re-render the DOM in the browser.

Partials

Handlebars partials are included either:

  • in other partial files with {{> <partial_name> (arg_name=arg_value..) }} syntax
  • in javascript files by:
    • importing at top of file using relative path e.g. import template from ./example.partial.hbs
    • compiling and capturing string in variable e.g. const strTemp = template(<context>)
      • context is object where properties become variables in partial file template

Javascript Files

To attach event handlers to the HTML created by handlebars, you'll need to add the templateString generated by the Handlebars method to the DOM. and then attach event handlers to the elements created.

e.g.

const template = require("<path_to_hbs_file>");

const context = { .. };
const strTemp = template(context, options?);

const container = document.getElementById("<container_id>");
container.innerHTML = strTemp;

container.querySelector("<interactive_element_selector>").onclick = () => { // Do stuff };

Dynamic Component Examples

A couple of notes on the Dynamic Component examples in this project.

Checkbox

Partial component with svg checkmark inside of checkbox. This can be used to add a stylized checkbox to the page rather than the standard checkbox created by the browser.

  • id - (default: undefined) only if set will add id and name attributes to <input> with this value
  • val - (default: true) sets the value of the <input> checkbox
  • checked - (default: false) sets checked status of checkbox
  • label - (default: '') clickable label text (toggles checkbox)
  • color - (default: 'black') color of the checkmark in the checkbox
  • bg - (default: 'white') background color of the checkbox
  • bg-checked - (default: bg || 'white') background color of the checkbox after checked
  • size - (default: '') size of checkmark (available values = sm, lg)

e.g.

/* `.hbs` file */
<span id="checkbox-1">
  {{> form/checkbox checked=complete color='white' bg='black' bg-checked='gray-200' label='Check Me' size='sm'}}
</span>

/* `.js` file */
document.querySelector('#checkbox-1 input').onchange = () => {
  // Do stuff
}

Note: Event handlers are attached in the javascript file to trigger this actually doing anything on the page

List Object and List item

Handlebars partials to render lists of items inside of html elements. The developer can specify a template partial that can be used to generate the contents of the list item (this should be a .hbs file).

Because this is a more complicated object (with content template) and likely requires event handlers for user interaction, it is best to create the ListObject inside of the javascript files and add it to the DOM from there.

e.g.

// Import from path to component
import listObjectTemplate from '../components/list/list-object.hbs';

// Build HTML from Template method
const listObject = listObjectTemplate({ items, template: 'my-list-template' }, {
  partials: {
    'my-list-template': listItemTemplate
  }
});

// Append HTML to Page
document.getElementById('page').innerHTML = listObject;

// Add event handlers...
listObject.querySelectorAll(<selector_of_element_with_event>).onclick = () => {
  // do stuff
};

Components Notes/Ideas

  • Seems like handlbars isn't great by itself for interactive applications
  • Can we add helpers that attach event handlers via some template context variable and a property on the element?
    • how about a class? constructor with params that associate class names with event handlers?
      • base class that creates DOM elements (with render function?) using:
        const temp = document.createElement('div');
        temp.innerHTML = template(context);
        return temp.innerHTML;
        
    • this is almost like recreating frameworks like these though (should test before we roll our own):
      • Angular
      • Aurelia
      • Ember
      • Inferno
      • Mithril
      • Svelte
      • Ractive
      • React
      • Vue
  • Backbone?

Actions and Githooks

This project uses github actions(github) to enforce actions/checks and workflow processes on github.

On Merge to Master

Whenever a PR is merged master, the documentation is updated based on the changes the user made in the commits. A git action(github) is set up to do the following:

  • Collect README files and update wiki
  • Fix/remove links in Wiki
  • Build documentation and generate commit

This Action requires the Git repo to have a secret configured in the Settings page named GH_PAT that contains a Github Personal Access Token from your user account: https://github.com/settings/tokens. There is also a reference to the repo name in the script, so you will need to ensure that this is updated if the repo name is different.

Master Build Status

This action checks the build status of the project and verifies that all of the tests and style linters are not failing. This occurs for every commit in a Github PR against the master branch, and for every commit (push) to the master branch on Github.

Runs:

  • linter
  • build (with webpack)
  • tests (karma)

Githooks

TODO...

Github Specific

Whenever a PR is made on Github, the body/description will be pre-populated with the contents in .github/PULL_REQUEST_TEMPLATE.md

Testing

You can run a single run of all unit tests in the application with npm run test. Or the tests will be run after the compilation has finished when running npm run start-watch. The testing is managed by Karma and configured using the karma.conf.js file(github)

This config file defines:

  • the testing framework we use (mocha)
  • the test bootstrap entry point(github)
  • The browser launchers that we use to run the Unit Tests (chrome)
  • The log level to display
  • Imports webpack config so we can also run the test and dev server if we use npm run start-watch

Bootstrap File

The testing bootstrap file(github) finds all of the test files and imports global objects that can be used in all tests to simplify each test file. It also creates a sinon Sandbox that is reset before each test for mocking/stubbing services and non-tested functions in the test context.

Global Variables (available in all .spec files):

  • sinonSandbox from Sinon
  • expect from Chai
  • TODO: describe/it/beforeEach from Mocha

Test Notes/Ideas

  • [ ] Cypress UI
  • [ ] Mock API requests?

Webpack Loaders

Webpack Loaders are used during the webpack build process to transpile the javascript and other asset types defined in the webpack.config.js(github).

Standard Loaders

This project uses basic loaders for most file types:

  • babel-loader and eslint-loader for javascript files
  • MiniCssExtractPlugin Loader, css-loader and postcss-loader for .css or .scss files
  • file-loader to include image files with html-webpack-plugin
  • svg-inline-loader for svg files (used in html-webpack-plugin)
  • underscore-template-loader for processing html files

Custom Loader Example

This project also includes one custom loader(github) to help generate handlebars template files. This is part of processing .hbs and .html files.

  • First we run the .hbs or .html file through the customsvg-icon-loaderwhich replaces@svg` imports with the contents of the svg file referenced (inlining the contents from svg files)
  • The function is then run through the next loader in the pipeline (defined in webpack.config.js)
    • handlebars-loader to return the handlebars template that is used to create the template() function that is imported when you require a .hbs file in the javascript code.
    • underscore-template-loader to generate the html entry file

Notes/Ideas

  • [x] Why are svg files included in output directory? they should be inlined I thought not happening anymore