Using Webpack

May 4, 2016

Introduction

Usual Problems

  • Multiple Web Requests -> Combining Files
  • Code Size -> Minifying Files
  • File order Dependencies -> Maintaining File Order
  • Transpilation
  • Linting

Other Solutions

  • Server-side tools
  • Task Runners : grunt, gulp

Webpack Solution

Can also combine css with the js.

  • Use NPM
  • Use a Module System
    • AMD
    • CommonJS
    • ES6 modules

Basic Builds

CLI Basics

npm install -g webpack
# bundle.js is tye typical name
webpack ./app.js bundle.js

Adding a config file

webpack.config.js it is a CommonJS module.

module.exports = {
    entry: "./app.js",
    output: {
        filename: "bundle.js"
    }
};

Watch Mode and the Webpack Dev Server

webpack --watch
module.exports = {
    entry: "./app.js",
    output: {
        filename: "bundle.js"
    },
    watch: true
};

Webpack Dev Server

npm install webpack-dev-server -g
webpack-dev-server --inline

Building Multiple Files

module.exports = {
    entry:  ["./app.js","./utils.js"],
    output: {
        filename: "bundle.js"
    },
    watch: true
};

Using Loaders

e.g.

npm install --save-dev babel-core babel-loader
// .babelrc
{
    "presets": ["es2015"]
}

ES6

// ...
module: {
    loaders : [
        {
            test: /\.es6$/,
            exclude: /node_modules/,
            loader: "babel-loader"
        }
    ]
},

resolve: {
    extensions: ['', '.js', '.es6']
}

Using Preloaders

JSHint

// ...
module: {
    preLoaders: [
        {
            test: /\.js$/,
            exclude: 'node_modules',
            loader: 'jshint-loader'
        }
    ],
    loaders : [
        {
            test: /\.es6$/,
            exclude: /node_modules/,
            loader: "babel-loader"
        }
    ]
},

resolve: {
    extensions: ['', '.js', '.es6']
}

Creating a Start Script

// package.json
  "scripts": {
    "start": "webpack-dev-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Production vs. Dev Builds

webpack -p # minified
# this module removes statements
npm install --save-dev strip-loader
var devConfig = require('./webpack.config.js');
var WebpackStrip = require('strip-loader');
var stripLoader = {
    test: [/\.js$/,/\.es6$/],
    exclude: '/node_modules/',
    // we are removing console.log statements
    loader: WebpackStrip.loader('console.log')    
};

devConfig.module.loaders.push(stripLoader);

module.exports = devConfig;
webpack --config webpack-production.config.js -p

Advanced Builds

Organizing Files and Folders

Using the webpack-dev-server the files are virtually created.

var path = require('path'); // this module is part of node

module.exports = {
    context: path.resolve('js'), // the root
    entry: ["./app.js","./utils.js"],
    output: {
        path: path.resolve('build/js/'),
        publicPath: 'public/assets/js/', // <-- 
        filename: "bundle.js"
    },
    devServer: {
        contentBase: 'public' // <--
    },
   
   // ..
};

Working with ES6 Modules

Check the babel-loader config section above, plus the .babelrc file.

Adding Source Maps

webpack -d
webpack-dev-server -d

Then you can use the debugger; statement.

Creating Multiple Bundles (e.g. laxy loading)

var path = require('path'); // this module is part of node
var webpack = require('webpack'); // <--

var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('shared.js'); // <--

module.exports = {
    context: path.resolve('js'), // the root
    entry: { // <--
        about: './about_page.js',
        home: './home_page.js',
        contact: './contact_page.js'
    },
    output: {
        path: path.resolve('build/js/'),
        publicPath: '/public/assets/js/',
        filename: "[name].js" // <--
    },
    plugins: [commonsPlugin], // <--
    
    // ...
};

Adding CSS

CSS and Style Loaders

npm install css-loader style-loader url-loader file-loader --save-dev
// require('./login');
import {login} from "./login";
import {} from "../css/bootstrap/css/bootstrap.css";
import {} from "../css/app.css";

login('admin','radical');

document.write("Hello World!!!");

console.log('App loaded');
var path = require('path'); // this module is part of node

module.exports = {
    context: path.resolve('js'), // the root
    entry: ['./app'],
    output: {
        path: path.resolve('build/js/'),
        publicPath: '/public/assets/js/',
        filename: "bundle.js"
    },
    
    devServer: {
        contentBase: 'public'
    },
    watch: true,
    module: {
       
        loaders : [
            {
                test: /\.es6$/,
                exclude: /node_modules/,
                loader: "babel-loader"
            },
            { // <-- for the css
                test: /\.css$/,
                exclude: /node_modules/,
                loader: "style-loader!css-loader"
            },
             { // <-- for the fonts
                test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
                loader: 'url-loader'
             }
        ]
    },
    
    resolve: {
        extensions: ['', '.js', '.es6']
    }
};

Using SCSS and SASS

npm install sass-loader node-sass --save-dev
var path = require('path'); // this module is part of node

module.exports = {
    context: path.resolve('js'), // the root
    entry: ['./app'],
    output: {
        path: path.resolve('build/js/'),
        publicPath: '/public/assets/js/',
        filename: "bundle.js"
    },
    
    devServer: {
        contentBase: 'public'
    },
    watch: true,
    module: {
        
        loaders : [
            {
                test: /\.es6$/,
                exclude: /node_modules/,
                loader: "babel-loader"
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                loader: "style-loader!css-loader"
            },
            {
                test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
                loader: 'url-loader'
            },
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                loader: "style-loader!css-loader!sass-loader"
            }
        ]
    },
    
    resolve: {
        extensions: ['', '.js', '.es6']
    }
};

Using LESS

// in loaders section
{
    test: /\.less$/,
    exclude: /node_modules/,
    loader: "style-loader!css-loader!less-loader"
}

Creating Separate CSS Bundle

npm install extract-text-webpack-plugin --save-dev
var path = require('path'); // this module is part of node

var ExtractTextPlugin = require('extract-text-webpack-plugin'); // <--

module.exports = {
    context: path.resolve('js'), // the root
    entry: ['./app'],
    output: {
        path: path.resolve('build/'), // <--
        publicPath: '/public/assets/', // <--
        filename: "bundle.js"
    },
    plugins: [
        new ExtractTextPlugin("styles.css") // <--
    ],
    devServer: {
        contentBase: 'public'
    },
    watch: true,
    module: {
        
        loaders : [
            {
                test: /\.es6$/,
                exclude: /node_modules/,
                loader: "babel-loader"
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                loader: ExtractTextPlugin.extract("style-loader","css-loader") // <--
            },
            {
                test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
                loader: 'url-loader'
            },
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                loader: ExtractTextPlugin.extract("style-loader","css-loader!sass-loader")
            }
        ]
    },
    
    resolve: {
        extensions: ['', '.js', '.es6']
    }
};

Auto Prefixer

npm install autoprefixer-loader --save-dev
var path = require('path'); // this module is part of node

module.exports = {
    context: path.resolve('js'), // the root
    entry: ['./app'],
    output: {
        path: path.resolve('build/js'),
        publicPath: '/public/assets/js',
        filename: "bundle.js"
    },
    devServer: {
        contentBase: 'public'
    },
    watch: true,
    module: {
        
        loaders : [
            {
                test: /\.es6$/,
                exclude: /node_modules/,
                loader: "babel-loader"
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                loader: "style-loader!css-loader!autoprefixer-loader" 
            },
            {
                test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
                loader: 'url-loader'
            },
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                loader: "style-loader!css-loader!autoprefixer-loader!less-loader"
            }
        ]
    },
    
    resolve: {
        extensions: ['', '.js', '.es6']
    }
};

Adding Images & Fonts to Your Build

We use the url-loader and play the limit parameter.

Adding Images

We require the image in the js or css.

npm install url-loader --save-dev
{
    test: /.(png|jpg)$/,
    exclude: '/node_modules/,
    loader: 'url-loader?limit=10000' 
    // bundles images below this size otherwize does a request for the image
}

Adding Fonts

npm install url-loader --save-dev
{
    test: /.(png|jpg|ttf|eot)$/,
    exclude: '/node_modules/,
    loader: 'url-loader?limit=10000' 
    // bundles images below this size otherwize does a request for the image
}

Webpack Tools

Connect Middleware

Node

  • ejs : templating system (one fo them)
  • express: web framework
  • morgan: logger facility
var webpackMiddleware = require('webpack-dev-middleware');
var webpack = require('webpack');

var config = require('./webpack.config');

app.use(webpackMiddleware(webpack(config), {
    publicPath: '/build',
    headers: {'X-Custom-Webpack-Header': 'yes'},
    stats: {
        color: true
    }
}));

Creating a custom loader

The loader it’s just a js module. Loaders deal with files.

loaders: [
    {
        test: /\.json$/,
        exclude: /node_modules/,
        loader: "path.resolve('loaders/strip')" // being strip the module we've created
    }
]

Using plugins

Plugins work on the bundle basis. Loaders with files seperately.

var webpack = require('webpack');
var TimestampWebpackPlugin = require('timestamp-webpack-plugin');

// ... 
plugins: [
    new webpack.ProvidePlugin({
        $: "jquery",
        jQuery: "jquery",
        "window.jQuery": "jquery"
    }),
    new TimestampWebpackPlugin({
        path: __dirname,
        filename: 'timestamp.json'
    }),
    new webpack.BannerPlugin("**************\nGenerated by webpack\n***************\n");
]