Project Resources - Build Pipeline

author: orionrush@gmail.com │ 28.04.19 last rev. 17.8.19


Overview

The following describes the asset pipeline for preparing images, JavaScript and SCSS/CSS for production. This pipeline tooling is platform independent, meaning that there shouldn't be any issues setting this up for your Mac/*nix/Windows environment (though I've been able to test this on windows yet).[1]

There are a few key technologies that allow us to automate the process of compiling assets, primary among them is the task runner Gulp.js, which we can script to process our project's assets. Gulp is built on the server-side JavaScript runtime node.js, which is tightly integrated with the package manager NPM. NPM is where we obtain the various modules required to process our assets. For our pipeline, these dependancies (other then Node and NPM) are installed locally to the project, so they shouldn't conflict with any globally installed versions you may have.

Gulp is a cross-platform, streaming task runner that lets developers automate many development tasks. At a high level, gulp reads files as streams and pipes the streams to different tasks. These tasks are code-based and use plugins. The tasks modify the files, building source files into production files. Google Progressive Web Aps

npm is the package manager for [the JavaScript based server] Node.js. It was created in 2009 as an open source project to help JavaScript developers easily share packaged modules of code.


Low level requirements

Minimum tested versions: [2][3]

The node -v and npm -v commands will display your current system versions.

As per the convention of Node and NPM, the required dependencies [including Gulp], are maintained and organised in a package.jason file, which should be kept under the project's version control. As npm installs and updates dependancies based on package.jason there is no need to install all the parts independently –– npm makes getting up and running as painless as possible.


Quick start

  1. Make sure you are using a compatible version of node and npm[4]
  2. Obtain the latest version of the ADRS project resources directory via version control.
  3. cd into the resources directory
  4. run:

Overview of Project commands

NOTES:

NPM scrip(s)

Gulp custom commands


Key files & directories

Once the project dependancies have been installed, the following is a breakdown of the key files you will find in the resources directory. Some of these will have been created during the npm install process.

Unless otherwise noted, the following should be kept under version control:

./                            // Server Root
├──┬─ /build-engine/
│  │
│  ├──┬ /test-includes/       // Template files for pages with the .inc extension
│  │  ├── various-templates.inc
│  │  └── ...
│  ├── page-elements.pinc     // Page element includes all have the .pinc extension 
│  ├── ...
│  └── PTDriver.php           // PHP templating build script
│	
└──┬ /resources/
│  │  
│  │
│  ├──┬ /dist/                // distribution directory created dynamically AFTER build command,
│  │  ├── /fonts/             // these are compiled assets which are moved here once processed
│  │  ├── /img/               // The /dist/**/* dir should be IGNORED by SVN/Git, and built locally **
│  │  ├── /js/  					 
│  │  └── ...					 				
│  │
│  ├──┬ /docs/                // *.md and *.html documentation for this project.
│  │  └── ...
│  │
│  ├──┬ /node_modules/        // npm dependancies, created dynamically after npm -install. 
│  │  └── ...                 // ** node_modules should be ignored by SVN/Git **
│  │
│  ├──┬ /src/                 // all style, js and image source files
│  │  ├── /fonts/             // local fonts
│  │  ├─┬ /img/               // website images
│  │  │ └── ...
│  │  │
│  │  ├─┬ /js/                // js
│  │  │ ├── *.js              // top level js entry points. * See note on js processing. 
│  │  │ ├── /vendor/          // third party js resources
│  │  │ ├── /dev/             // custom scripts and libs created for this project.
│  │  │ └── /overleap/			// scrips or libraries which should not be processed or compressed by Babble, Terser etc. We strongly suggest these be minified versions.
│  │  │	
│  │  └─┬ /styles/
│  │    ├── *.scss            // Top level entry point for styles. 
│  │    └┬ /scss/             // ** See documentation on SCSS structure and approach in the /docs/ directory. **
│  │     ├── /layers/
│  │     ├── /shame/
│  │     ├── /tools/
│  │     └── /vendor/
│  │
│  ├─ gulpfile.js             // gulp task-runner instructions
│  ├─ package.json            // the npm package descriptions for all our gulp dependancies.
│  ├─ package-lock.json	       // Used by npm to record changes to modules & package.json
│  │	
│  │  /** preferences files */
│  ├─ .eitorconfig            // Editor config file for editing envs that support it.*
│  ├─ .eslintignore           // Ignore directives for eslint
│  ├─ .eslintrc.json          // eslint configuration 
│  ├─ .prettierignore         // Ignore directives for prettier
│  ├─ .stylelintrc            // sylelint configuration
│  └─ .stylelintignore        // Ignore directives for stylelint
│
└─ *.html						// Various generated static template files ** these should be ignored by SVN/Git **	

.editorconfig plugins can be found for all major editors including jEdit and Atom.


Adding resources

This section will focus on the adding of JavaScript resources, as we use special tooling to make it easier. This said, the process pipelines listed in the following sections help illustrate the process by which assets are consumed. The concept of 'entry points' is key to understanding what should go where. Simply put, an entry point is a file which imports or otherwise links to additional resources. A .scss or .js processing pipeline doesn't have to render every file in a file tree but instead the processor looks for entry point files in a specified directory, and then follows any chain of import('bar.scss') directives to combine all linked files into a bar.css file.

/src/styles/*.{css,scss}

The watch directory for our s/css pipeline is: /resources/src/styles/*.{css,scss}

Any css or scss file found in the /resources/src/styles/ directory will be processed. The processed css file will be found in /resources/dist/styles/bar.css.

/src/js/*.{js,jsx,es,es6}:

The watch directory for our JavaScript pipeline is:/resources/src/js/*.{js,jsx}

Any *.js, *.jsx, *.es, *.es6 files found in the /resources/src/js/ directory will be processed. The processed JavaScript files will be found in /resources/dist/js/*.js.

/src/js/overleap/*.{js,map}

We've discovered that some dependancies within the project use conventions that throw errors with Browserify. For these instances, we've created the src/js/overleap/ directory. Any *.js files placed here will be copied without alteration to /dist/js/*.js.

This in this way we can begin to take the distribution directory /dis/**/ out of version control.

Note: It is highly recommended that you use pre-minified versions of these files, and where available, associated *.js.map for better browser debugging.

But wait!

JavaScript ES5 doesn't natively have import('baz.js') functionality! While the concept of programmatic loading of modules was brought ES6+, much of our codebase isn't there yet. In fact, to take advantage of ES6 modules would require("intentional-pun.js") a lot of refactoring. In our JS pipeline we use a tool called Browserify which implements for regular JS, a familiar import api as you would use with CSS. Unfortunately (at least for migration ease) the authors of ES6 took a different, though more powerful approach, so the Browserify approach is not the equivalent to ES6. Until the projects js codebase is fully brought up to modern standards, Browserify will be a core deployment dependency.


Browsers Targeted

Currently the selected browser market profile is defined using the browserlist tool, which creates a shared target list for tools like Autoprefixer, StyleLint, and Babel.

// package.json

"browserslist": [
	"> 0.2%",
	"last 2 versions",
	"Firefox ESR",
	"IE 10",
	"not dead"
] 

This targets browsers that command greater then .2% market share, the last two versions of those browsers, Firefox ESR (Extended Service Release, used by a lot of governments, schools and other orgs), and IE 10. Lastly, the ruleset also omits any browsers that are marked as 'dead'.


SCSS pipeline

Scripts runs gulp cleanCSS prior to processing files, which deletes the contents of the dist/styles directory.

Command: gulp css

/src/styles/*.{scss,css} -> {sourcemaps}-{scss}-{postcss-[autoprefixer]-[mqpacker]} -> dest/styles/

Command: gulp css --prod

/src/styles/*.{scss,css} -> {sourcemaps}-{scss}-{postcss-[autoprefixer]-[mqpacker]-[bless]-[cssnano]} -> dest/styles/

Note: Style CSS/SCSS entry points are only gathered from the top level of /dist/styles/.

npm run lintStyles

"lintStyles": "stylelint -q --fix  './src/styles/**/*.{css,scss}' "

/src/styles/**/*.{scss,css} -> {stylelint} ->/src/styles/
                                  │
                   {prettier-as-plugin -> stylelint}
                                  │
                            (.stylelintrc)
           {
            "plugins": [],
            "extends": [
                  "stylelint-config-recommended-scss",
                  "stylelint-config-wordpress/scss"
            ],
            "rules": {
                  "no-descending-specificity": null
            },
            "exports": {
                  parser: 'postcss-scss'
            }
           }	


JS pipeline

Scripts runs gulp cleanJS prior to processing files, which deletes the contents of the dist/js directory.

Command: gulp js

/src/js/**/*.{js,jsx} -> {browserify-[babelify]}-{postcss-[autoprefixer]-[mqpacker]} -> dest/styles/

Command: gulp js --prod

/src/js/**/*.{js,jsx} -> {browserify}-{sourcemaps}-{scss}-{postcss-[autoprefixer]-[mqpacker]-[bless]-[cssnano]} -> dest/styles/

Note: JS entry points are only gathered from the top level of /dist/js/*.js.

Command: npm run lintJS

"lintJS": "prettier-eslint --write './src/js/**/*.{js,jsx}' ",

/src/js/**/*.{js,jsx} -> {prettier-eslint} ->/src/js/
                                  │
            formats with prettier followed by eslint --fix
                          (.eslintrc.json)
				
			{
			    "plugins": ["prettier", "promise"],
			    "extends": [
			        "standard",
			        "plugin:promise/recommended",
			        "plugin:prettier/recommended"
			    ],
			    "parserOptions": {
			        "ecmaVersion": 8
			    },
			    "rules": {
			        "no-console": "off",
			        "prettier/prettier": "error"
			    },
			    "globals": {
			        "Atomics": "readonly",
			        "SharedArrayBuffer": "readonly",
			        "requestAnimationFrame": true,
			        "sessionStorage": true
			    },
			    "env": {
			        "browser": true,
			        "es6": true,
			        "amd": true,
			        "jquery": true,
			        "commonjs": true
			    }
			}
			

## Key gulpfile npm plugins:

A basic description of the key gulp plugins used by this project:

####JavaScript:

CSS:

Images:

Other & Linting:


package.json component overview

	// as of 28/4/2019

    "@babel/core": "^7.4.4",                        // Bable core plugin
    "@babel/preset-env": "^7.4.4",                  // A smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms/polyfills are needed
    "acorn": "6.0.0",                               // A peer dependancy of browserify
    "autoprefixer": "^9.5.1",                       // automatically includes browser prefixes so you don't have to
    "babelify": "^10.0.0",                          // Browserify transform for Babel
    "browser-sync": "^2.26.5",                      // Let you hot-inject updated css, or trigger a browser reload after a file change
    "browserify": "^16.2.3",                        // Browserify lets you require('modules') in the browser
    "browserify-shim": "^3.2.2",                    // Makes CommonJS incompatible files browserify-able.
    "browserslist": "^4.5.5",                       // Share target browsers between different front-end tools,
    "css-mqpacker": "^7.0.0",                       // Pack same CSS media query rules into one using PostCSS [deprecated]
    "cssnano": "^4.1.10",                           // A modular minifier, built on top of the PostCSS ecosystem.
    "del": "^4.1.1",                                // Delete files and folders
    "eslint": "^5.16.0",                            // JavaScript linting
    "eslint-config-wordpress": "^2.0.0",            // ESLint shareable config for WordPress js standards
    "eslint-plugin-import": "^2.17.2",              // Support linting of ES2015+ (ES6+) import/export syntax,
    "eslint-plugin-promise": "^4.1.1",              // Enforces best practices for JavaScript promises.
    "gulp": "^4.0.1",                               // The main gulp tool 
    "gulp-bless": "^4.0.1",                         // CSS post-proc splits CSS files for IE < 10
    "gulp-connect-php": "^1.0.3",                   // Start a PHP-server (not in use yet)
    "gulp-dest-clean": "^0.5.0",                    // Deletes the contents of the destination folder
    "gulp-headerfooter": "^1.0.3",                  // Add header and footer content to files - in this case used for .md files
    "gulp-if": "^2.0.2",                            // Conditional operations for gulp
    "gulp-imagemin": "^5.0.3",                      // Image min for gulp
    "gulp-line-ending-corrector": "^1.0.3",         // Makes line endings consistent between Win/*nix
    "gulp-markdownit": "^1.0.3",                    // Render MarkDown files into HTML
    "gulp-newer": "^1.4.0",                         // Only pass through newer source files
    "gulp-notify": "^3.2.0",                        // Gulp plugin to send messages to osx/win/linux notifier
    "gulp-options": "^1.1.1",                       // Allow to pass gulp options and retrieve them
    "gulp-plumber": "^1.2.1",                       // Prevent pipe breaking caused by errors from gulp plugins
    "gulp-postcss": "^8.0.0",                       // PostCSS plugin to pipe CSS through several plugins, but parse CSS only once.
    "gulp-rename": "^1.4.0",                        // Add prefixes etc
    "gulp-sass": "^4.0.2",                          // Gulp sass wrapper
    "gulp-size": "^3.0.0",                          // Display the size of your project
    "gulp-sourcemaps": "^2.6.5",                    // Source maps for js and css
    "gulp-tap": "^1.0.1",                           // Offers the ability to selectively filter contents in a pipeline, we use it to replace file contents with using browserify.
    "gulp-terser": "^1.1.7",                        // JS compression
    "gulplog": "^1.0.0",                            // Logging
    "markdown-it-anchor": "^5.0.2",                 // Extending markdown rendering support
    "markdown-it-footnote": "^3.0.1",
    "markdown-it-table-of-contents": "^0.4.4",
    "markdown-it-toc-and-anchor": "^4.2.0",
    "markdown-it-wiki-toc": "^1.0.4",
    "path-exists": "^4.0.0",                        // provides a Boolean response for path existence
    "postcss-reporter": "^6.0.1",                   // postcss plugin
    "postcss-sass": "^0.3.5",                       // SASS parser -- 
    "prettier": "^1.17.0",                          // We use this as a plugin in StyleLint, otherwise prettier-eslint
    "prettier-eslint": "^8.8.2",                    // Formats JavaScript using prettier followed by eslint --fix
    "prettier-eslint-cli": "^4.7.1",                // CLI interface for prettier-eslint
    "prettier-stylelint": "^0.4.2",                 // Interface for passing styles to prettier before stylelint
    "shx": "^0.3.2",                                // Portable Shell Commands for Node
    "stylelint": "^10.0.1",                         // Using for linting as a npm run script (calling prettier as a plugin)
    "stylelint-config-recommended-scss": "^3.3.0",  // (.stylelintrc) this may overlap with stylelint-config-wordpress
    "stylelint-config-wordpress": "^14.0.0",        // (.stylelintrc) WordPress shareable config for stylelint.
    "vinyl-buffer": "^1.0.1",                       // Convert streaming vinyl files to use buffers
    "vinyl-paths": "^2.1.0",                        // Get the file paths in a vinyl stream
    "vinyl-source-stream": "^2.0.0"                 // Use conventional text streams at the start of your gulp or vinyl pipelines

Known issues:

  1. While installing the project you may see several "vulnerabilities":

As the developer is not responding, I have created a patch and pull request, which we are using in the meantime.


  1. This manual assumes a basic familiarity with use of the command line and common operations such as cd, ls and mkdir. ↩︎

  2. If you need to get npm and Node installed on your system, npmjs.org has some good tutorials: setting up your local environment. ↩︎

  3. As of this writing, node-sass is not yet compatible with the latest version of node.js v 12.0. So we recommend installing the latest LTS (long term support) version of node.js which is 10.15.3. This Stackoverflow thread covers a number of ways you can roll back to a previous version of node.js. Of special interest is the node version manager. ↩︎

  4. We've chosen to do linting via npm rather then the gulp pipeline, for a variety of reasons, but due to these issues and general maintainability concerns, we will just use the well supported parent projects via npm rather then gulp wrappers, because it-just-works™. ↩︎ ↩︎

  5. --prod flag can be applied to any build command ie gulp css --prod and gulp js --prod. Doing so will cause the output to be minified etc. In the case of styles, they are additionally passed through Bless to circumvent selector number limit imposed by IE > 9. (Pub trivia: IE9 has an upper limit of 4095 selectors, after which it silently fails). ↩︎