Created by Jared Siirila / @jtsiirila
Overview
History
Modern Workflow & Tool Categories
Some Suggestions
New Features
A brief, mostly inaccurate, and subjective history of front-end tooling
Simple
Backend does 99% of the work
Microsoft creates XMLHTTP
Mozilla creates XMLHTTPRequest
The beginning of AJAX
Dark Ages
JS Renaissance
JS Frameworks Gen 1
With great power, comes the need for great tooling!
Somebody...maybe
We build complex web applications using our great new frameworks
Modules
JavaScript Nowhere
JavaScript Everywhere
JS Industrial Revolution
Let's go to the SPA
We still dislike JS, just not as much
You got your JS in my markup
You got your markup in my JS
JS is looking a little dated, how about a refresh
Oops...we forgot to include the module loader spec for browsers
Maybe we should create a proper JS module format?
Code quickly/efficiently
Leverage existing libraries
Reduce errors
Consistency
Build quickly
Optimize for browsers
Rapid iteration
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.
, including reuse of critical libraries like left-pad
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
All the good JS names were taken
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"
}
}
Ships with over 200 available rules
Supports plugins to add additional rules
Can be configured multiple ways
'eslintConfig' section in package.json
Command line arguments
Comments in source files
Configuration Cascading
Subfolders can have different linting rules
Reduce errors
Prevent regressions
Validate interfaces
Refactor confidently
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
// 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;
}
}
/* 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;
}
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
PostCSS
Creates an Abstract Syntax Tree (AST) from the input files that can be processed by plugins
Plugins can do nearly anything including:
A tool for transforming styles with JS plugins
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
}
Kicking Sass: How To Write CSS (and JS) in a PostCSS World
Aaron Ladage
Sunday - 1:30-2:20 - Room 2207
Source Map Files
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
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
Write JS to perform the tasks. You control the order tasks are run in and almost all other aspects of the task runner.
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']);
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
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
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
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.
Webpack
Rollup.js
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!
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()]
}
Filetype loaders
Transpile loaders
Only add a tool when needed
Wrap underlying tools in a common interface
Convention
Delegate
Abstraction
[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] 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
[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!!
[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
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'",
}
Maintain app state
No browser refresh
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.
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 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
An ES6 aware minifier that is a plugin to the Babel transpiler
Created by Jared Siirila / @jtsiirila