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,845