Stocking a Modern Front-end Toolshed
Created by Jared Siirila / @jtsiirila
Outline
Overview
History
Modern Workflow & Tool Categories
Some Suggestions
New Features
Tool Types Covered
- Generator
- Package Manager
- Linter
- Module Loader/Bundler
- Concatenator & Minifier
- JS Transpiler
- CSS Preprocessor
- Task Runner/Build System
Not Covered
- Editors
- Source Control
- Testing
- Frameworks
- Continuous Integration
History
A brief, mostly inaccurate, and subjective history of front-end tooling
Before 1999
Simple
Backend does 99% of the work
1999 & 2000
Microsoft creates XMLHTTP
Mozilla creates XMLHTTPRequest
The beginning of AJAX
2000-2004
Dark Ages
2005 & 2006
JS Renaissance
JS Frameworks Gen 1
With great power, comes the need for great tooling!
Somebody...maybe
2007 - 2009
We build complex web applications using our great new frameworks
2009
Modules
- CommonJS
- AMD
JavaScript Nowhere
- CoffeeScript
JavaScript Everywhere
- Node
2010
JS Industrial Revolution
Let's go to the SPA
- Angular
2012
We still dislike JS, just not as much
- TypeScript
2013
You got your JS in my markup
You got your markup in my JS
- JSX & React
2015
JS is looking a little dated, how about a refresh
- ES6/ES2015
Oops...we forgot to include the module loader spec for browsers
Maybe we should create a proper JS module format?
- ES6 modules
Goals
Code quickly/efficiently
Leverage existing libraries
Reduce errors
Consistency
Build quickly
Optimize for browsers
Rapid iteration
Initial Architecture
Develop
Test
Build
Intial Architecture
- Rapid start
- Define folder structure
- Modular/Reusable
- Consistency
-
Dependencies
- Find
- Acquire
Generators/Scaffolding
Yeoman
Framework Provided
angular-cli (Angular 2)
ember-cli
Allow for rapid creation of the structure of an application. Generators can create an entire application framework, or can create individual components of an application.
Package Manager
- Reduce
- Reuse
Recycle- Update
, including reuse of critical libraries like left-pad
- Frustrating Windows devs due to the 260 character path limit
jspm
Bower
Duo
Package Manager
Develop
- Code efficiently
- Minimize errors
- Consistent style
- Rapid iteration
- Update dependencies
Tooling
- Editor
- Linter
-
Dev Server
- Live reload / Hot reload
- Watch & rebuild
- Package Manager
Linter
Enforce style consistency and catch minor issues at compile time
Finally, a way to end the tabs vs spaces arguments and make my whole team indent code properly (with tabs of 3 spaces)
Like a dryer lint trap, a linter catches small items that individually are mostly harmless, but collectively can add up to a big problem
Particularly useful in a dynamic, weakly typed, and quirky language like JavaScript
When you want a heartless tool to enforce consistency in the least important aspects of your JS
Linter
ESLint
All the good JS names were taken
Types of Issues a ESLint Catches
var foo = "foo";
const bar = "bar";
if (foo = "bar") { // Accidental assignment in a conditional statement
debugger; // Forgotten debugger statements left in code
var hello = "world"; // Disallow variable declaration in a nested block
typeof foo === "stirng"; // Only allow valid strings in typeof comparisons
} // Inconsistent indentation
bar = "foo"; // ES6 const reassignment
// And much much more
// In fact, it can catch almost anything since it uses Esprima
// to parse the code and create an Abstract Syntax Tree to use for linting
{
"env": {
"browser": true, // Defines predefined global variables
"es6": true // You can set more than 1 environment
},
"extends": "eslint:recommended", // Enables a subset of core rules for common problems
"parserOptions": {
"sourceType": "module", // Can be "script" or "module", module denotes ES6 modules
"ecmaVersion": 6 // Specifies the version of JS to use
},
"rules": { // Rules can be "off" or 0, "warn" or 1, and "error" or 2
// Enable additional rules beyond the eslint:recommended
"indent": ["error", 4],
"linebreak-style": ["error", "unix"],
"quotes": ["warning", "double"],
"semi": ["error", "always"],
// Override default options for rules from base configurations
"comma-dangle": ["error", "always"],
"no-cond-assign": ["error", "always"],
// Disable rules from base configurations
"no-console": "off"
}
}
Other ESLint Features
-
Ships with over 200 available rules
-
Supports plugins to add additional rules
-
Can be configured multiple ways
- .eslintrc.* file
-
'eslintConfig' section in package.json
-
Command line arguments
-
Comments in source files
-
Configuration Cascading
-
Subfolders can have different linting rules
-
Test
Reduce errors
Prevent regressions
Validate interfaces
Refactor confidently
Build
- Optimize
- Build quickly
- Debuggable output
-
Customize for environment
- Development
- Test
- Production
CSS Preprocessor
Why?
CSS is primitive
Duplication everywhere
No Variables
Unorganized
Duplication everywhere
Duplication everywhere
Duplication everywhere
Duplication everywhere
Duplication everywhere
-moz-o-ms-webkit-vendor-prefixes
Duplication everywhere
CSS Preprocessor
Sass
// Importing other SCSS files
@import 'reset';
// Variables!! Yay!
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
$secondary-color: #042;
body {
font: 100% $font-stack;
color: $primary-color;
}
nav {
// Nesting of rules
ul {
margin: 0;
padding: 0;
list-style: none;
// Referencing the parent selector
&.category {
color: $secondary-color;
}
}
a {
display: block;
}
}
CSS
/* Rules from reset.scss */
body {
font: 100% Helvetica, sans-serif;
color: #333;
}
nav ul {
margin: 0;
padding: 0;
list-style: none;
}
nav ul.category {
color: #042;
}
nav a {
display: block;
}
CSS Preprocessor
There are things a traditional CSS Preprocessor doesn't handle well:
Adding browser specific prefixes
Not extensible
Work with inline styles in JS files
Use future CSS syntax today
CSS Preprocessor
PostCSS
PostCSS
Creates an Abstract Syntax Tree (AST) from the input files that can be processed by plugins
Plugins can do nearly anything including:
- Creating browser prefixed styles
- Linting the CSS
- Creating unique selectors for CSS embedded in JS components
- Adding support for variables, loops, math, conditional statements, etc
A tool for transforming styles with JS plugins
Autoprefixer
a {
transition: transform 1s
}
a {
-webkit-transition: -webkit-transform 1s;
transition: -ms-transform 1s;
transition: transform 1s
}
a {
background: linear-gradient(to top, black, white);
display: flex
}
::placeholder {
color: #ccc
}
a {
background: -webkit-linear-gradient(bottom, black, white);
background: linear-gradient(to top, black, white);
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex
}
:-ms-input-placeholder {
color: #ccc
}
::-moz-placeholder {
color: #ccc
}
::-webkit-input-placeholder {
color: #ccc
}
::placeholder {
color: #ccc
}
More Information
Kicking Sass: How To Write CSS (and JS) in a PostCSS World
Aaron Ladage
Sunday - 1:30-2:20 - Room 2207
Transpile JS
TSC
Coffee
Concatenate/Minify
UglifyJS
Source Maps
Source Map Files
Task Runners
These are the big boy of build tools. They allow you to perform nearly any build step, but tend to require extensive configuration to setup.
A single approach to launch all of the build tasks including
Linting
Testing
Transpiling
Preprocessing
Compilation
Concatenation
Minification
Copying files
Watching for changes
Development Server
Live Reload
Optimize Images
Install Dependencies
Etc
Configuration
A configuration file specifies the types of tasks to perform, but leaves the details of how the tasks are executed up to the task runner
Declarative
Write JS to perform the tasks. You control the order tasks are run in and almost all other aspects of the task runner.
Two Main Types
Grunt
grunt.initConfig({
less: {
development: {
files: {
"build/tmp/app.css": "assets/app.less"
}
}
},
autoprefixer: {
options: {
browsers: ['last 2 version', 'ie 8', 'ie 9']
},
multiple_files: {
expand: true,
flatten: true,
src: 'build/tmp/app.css',
dest: 'build/'
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.registerTask('css', ['less', 'autoprefixer']);
Gulp
var gulp = require('gulp'),
less = require('gulp-less'),
autoprefix = require('gulp-autoprefixer'),
jshint = require('gulp-eslint'),
uglify = require('gulp-uglify'),
concat = require('gulp-concat');
gulp.task('css', function () {
gulp.src('assets/app.less')
.pipe(less())
.pipe(autoprefix('last 2 version', 'ie 8', 'ie 9'))
.pipe(gulp.dest('build'));
});
gulp.task('js', function () {
return gulp.src('js/*.js')
.pipe(plugins.eslint())
.pipe(plugins.jshint.reporter('default'))
.pipe(plugins.uglify())
.pipe(plugins.concat('app.js'))
.pipe(gulp.dest('build'));
});
Grunt and Gulp are by far the most popular front-end tasks runners, however there are other task runners/build tools
Broccoli
FlyJS
Make
Brunch
Includes a scaffolding generator
Configuration, but with a lot of conventions built in
Broccoli
Abstracts with trees instead of files
Used by the Ember cli
Declarative (like Gulp)
FlyJS
Built using ES6 generators
Mimosa
Includes a scaffolding generator
Configuration (like Grunt), but with a lot of conventions built in
Opinion
Use npm scripts to abstract away any build tool or task runner used Only include one of these tools if your build has complex requirements that can't be fulfilled with simpler tools
Module Loaders/Bundlers
JavaScript didn't have a native module concept until ES6, so when JS frameworks started to become popular people worked on module specs for JS.
AMD and CommonJS modules were a huge leap forward for JS development, but they added the need for module loaders.
Module Loaders
Webpack
Rollup.js
Module Loaders
Webpack
Webpack
CommonJS and AMD module support out of the box
ES6 modules with a plugin, Webpack 1, or out of the box with Webpack 2.0
Multiple bundle generation
On demand loading of additional code chunks
Includes a file watcher
Minimize code with uglify
Plugins & Loaders
With loaders everything is a Module!
Dependencies
require('./button.css');
const icon = require('ok.svg');
import React from 'react';
const ConfirmButton = React.createClass({
render() {
return (
<Button class="confirm" title="OK">
<img src={icon} />
</Button>
);
}
});
var webpack = require('webpack');
var path = require('path');
var autoprefixer = require('autoprefixer');
var less = require('less');
module.exports = {
entry: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./src/editor.main.jsx'
],
output: {
path: path.join(__dirname, 'build'),
filename: 'editor.bundle.js',
sourceMapFilename: '[file].map',
publicPath: '/build/'
},
devtool: 'source-map',
resolve: {
extensions: ['', '.js', '.jsx']
},
module: {
preLoaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'eslint'
}
],
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'react-hot!babel'
},
{
test: /\.(less|css)$/,
loader: 'style-loader!css-loader!postcss-loader!less-loader',
exclude: /node_modules/
},
{
test: /\.(svg|png|jpg|jpeg|gif)$/,
loader: 'file',
exclude: /node_modules/
}
]
},
postcss: function () {
return [autoprefixer];
},
plugins: [new webpack.HotModuleReplacementPlugin()]
}
Loader Examples
Filetype loaders
- JSON
- CSV
- SVG
- XML
Transpile loaders
- Elm
- Typescript
- Babel
How
Defer
Only add a tool when needed
Convention
Delegate
- Use presets
- eslint:recommended or AirBnB ESLint rules
- Let someone else decide
- Use the suggested/preferred tools for your framework
- Choose multi-functional tools
Abstraction
Wrap underlying tools in a common interface
Framework CLI
Convention
Delegate
Abstraction
Great Framework CLIs
- ember-cli
- angular-cli (Angular 2)
- vue-cli
- create-react-application
Ember-cli
[X] Generators
[X] Lint
[X] Development Server
[X] Live Reload
[X] Watch
[X] Test
[X] Transpile
[X] Preprocess
[X] Minify
[X] Concatenate
[X] Source Maps
Angular-cli
[X] Generators
[X] Lint
[X] Development Server
[X] Live Reload
[X] Watch
[X] Test
[X] Transpile
[X] Preprocess
[X] Minify
[X] Concatenate
[X] Source Maps
[X] Optimize Images
Create-react-app
[X] Generators
[X] Lint
[X] Development Server
[X] Hot Reload
[X] Watch
[X] Test
[X] Transpile
[X] Preprocess
[X] Minify
[X] Concatenate
[X] Source Maps
[X] Optimize Images
Provides an eject button!!
Vue-cli
[X] Lint
[X] Development Server
[X] Live Reload
[X] Hot Reload
[X] Watch
[X] Test
[X] Transpile
[X] Preprocess
[X] Minify
[X] Concatenate
[X] Source Maps
Roll Your Own
Start with
npm
Webpack
Babel
PostCSS / Sass
Use npm scripts to launch all build steps
"scripts": {
"build:js": "browserify assets/scripts/main.js > dist/main.js",
"watch:js": "watch 'npm run build:js' assets/scripts/",
"build:css": "stylus assets/styles/main.styl > dist/main.css",
"watch:css": "watch 'npm run build:css' assets/styles/",
"build:html": "jade index.jade > dist/index.html",
"watch:html": "watch 'npm run build:html' assets/html",
"build": "npm run build:js && npm run build:css && npm run build:html",
"build:watch": "parallelshell 'npm run watch:js' 'npm run watch:css' 'npm run watch:html'",
}
New Features
Hot Module Replacement
Live reload, but much better
Live Reloading
Glorified F5
Issue
- Lose app state
Hot Module Replacement
Maintain app state
No browser refresh
Requirements
- Module Loader
- Development server
- Polling/sockets
- State separate from logic
ESLint Auto-fix
ESLint supports a --fix cli parameter that will automatically attempt to fix a subset of lint rules that it is able to try and fix.
- There are ~50 rules it can auto-fix
- The list grows with each release
- It is able to do this because of the AST parsing it does
Tree Shaking 🌲
Save bytes
Tree shaking creates dead code.
-Tobias Koppers
Tree shaking excludes exports from modules where it can be detected the export is not used. Uglify then deletes the dead code.
-Kent Dodds
Tree Shaking
Tree shaking removes a unused exports in ES6 modules from your built code.
Originally came from Rollup.js
Only works with ES6 module since they have static imports and exports. Doesn't work with CommonJS or AMD
// maths.js
// This function isn't used anywhere, so
// it is excluded from the bundle...
export function square ( x ) {
return x * x;
}
// This function gets included
export function cube ( x ) {
// rewrite this as `square( x ) * x`
// and see what happens!
return x * x * x;
}
// main.js
import { cube } from './maths.js';
console.log( cube( 5 ) ); // 125
// Output
// This function isn't used anywhere, so
// Rollup excludes it from the bundle...
// This function gets included
function cube ( x ) {
// rewrite this as `square( x ) * x`
// and see what happens!
return x * x * x;
}
console.log( cube( 5 ) ); // 125
Babili (babel-minify)
An ES6 aware minifier that is a plugin to the Babel transpiler
Stocking a Modern Front-end Toolshed
Created by Jared Siirila / @jtsiirila
tooling
By Jared Siirila
tooling
- 1,850