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:
- The README files in the repo,
- The Github Wiki, and
- The generated doc site served when running the server and on github pages
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 Github 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
- importing at top of file using relative path e.g.
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
andname
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
- Handlebars recommends some frameworks: https://github.com/devlinjunker/template.webpack.fend/pull/23#issuecomment-629585403
- Reactive-Handlebars looks interesting, but hasn't been updated in years...
- 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;
- base class that creates DOM elements (with
- 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
- how about a class? constructor with params that associate class names with event handlers?
- 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.
Label Manager
This project defines the Github Labels in a YAML file that is managed by the Github Labeler Action. Any labels that are not defined in this file will be removed every time this action is run. This does not affect PRs
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):
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 custom
svg-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
- handlebars-loader to return the handlebars template that is used to create the
Notes/Ideas
- [x]
Why are svg files included in output directory? they should be inlined I thoughtnot happening anymore