author: orionrush@gmail.com │ 28.04.19 last rev. 17.8.19
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.
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.
node
and npm
[4]resources
directory via version control.cd
into the resources
directorynpm install
- This updates and loads any missing packages found in package.json
.sudo npm ci
to run a clean install of all node project packages found in package.json
.NOTES:
resources
directory..prettierignore
or .exlintignore
(should be the same). Ignore patterns skip the contents of any /vendor/
directories, and any files with "min" extention ie: **/*-min.js
or **/*.min.css
.npm
script rather than gulp
for linting.[4:1]npm run clean
- Deletes the \dist\
directory. The \dist\
directory will be recreated after any other gulp command that generates output.
npm run lintStyles │ lintJS
-- WARNING FILE OVERWRITE. Prettify and lint either JS (ESlint) or SCSS (StyleLint). Run before committing JS and styles to repo for code consistency and readability.
gulp
- The default task, which runs the full suite of tasks in development
mode, including instantiating PHP's built in server and starting file watching with BrowserSync live-reload.
gulp --prod
[5] - Same as gulp
, but all relevant files go through additional minification steps for production. The PHP server, file watching and live-reload are not activated.
gulp clean
- Deletes the dist
directory. Same as npm run clean
gulp cleanCSS
- removes the contents of the /styles/
directory.
gulp css
- This task runs cleanCSS
prior to running all SCSS processing tasks, starting with any top level entry point /styles/*.scss/
. As a parallel process gulp css
also triggers gulp images
.
gulp fonts
- Copies the fonts directory to the dist
directory
gulp images
- Runs images through imagemin
for smaller file sizes. imagemin
is smart enough not to process files that haven't changed since the last run.
gulp cleanJS
- Removes the contents of the /js/
directory
gulp js
- Runs cleanJS
prior to running all js compilation tasks. It will process ALL top level js files /js/*.js
.
gulp md
- Processes any markdown (.md) files found in the /docs/
directory and converts them to html files, within the same directory. This action is not currently included in any file watching/live reload related processes.
gulp watch
- Starts a series of watch processes in the /src/
directory. Any file or path change will trigger the relevant processing task. Also creates a temporary PHP development server, and starts BrowserSync for live reloading.
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.
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.
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
.
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
.
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.
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.
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'.
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'
}
}
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:
import("xx.js")
just as you would with css. Yes ES6 style imports could have also been used, but this would require updating a lot of legacy js.tachyons
has a lot of overlapping queries across its footprint (for legibility). This plugin will concatenate them depricated-look-for-replacement.gulp-sourcemaps used for making js and css source-maps.
For file formatting npm run lintStyles | lintJS
we pass js s/css resources through Prettier before passing them to either Stylelint or ESLint for S/CSS or javascript respecively. These operations are done in place, your files will be overwritten.
Code formatting rules based on Wordpress coding standards for CSS and JS.
// 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
tar
package by the following packages:
tar
element, introduced some breaking changes to the note-sass project, so this may take some time to patch. For development this is fine, as long as we're not running and older version of tar
on a live server we're okay.braces
. We are currently using prettier-stylelint-temp which resolves old dependancies in the original package which have yet to be corrected. As the developer is not responding, I have created a patch and pull request, which we are using in the meantime.
This manual assumes a basic familiarity with use of the command line and common operations such as cd
, ls
and mkdir
. ↩︎
If you need to get npm and Node installed on your system, npmjs.org has some good tutorials: setting up your local environment. ↩︎
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. ↩︎
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™. ↩︎ ↩︎
--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). ↩︎