Webpack

Kaj Białas

Daniel Capeletti

Artur Kudeł

Michał Przyszczypkowski

01.12.2016

Presentation plan

  • What is Webpack?

  • How it works?

  • Options

  • Loaders (how it works?, how to write my own, common loaders)

  • Plugins (how it works?, how to write my own, common loaders)

  • Common scenarios (code splitting, multiple entry points, source maps ...? )

What is Webpack

How does it work?

  • An ES2015 import statement

  • A JavaScript require() statement

  • An AMD define and require statement

  • An @import statement inside of a css/sass/less file.

  • An image url in a stylesheet (url(...)) or html (<img src=...>) file.

What is a webpack Module

Graph dependencies

http://webpack.github.io/analyse

--profile --json >> stats.json

Code example

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			exports: {},
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}


/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "/";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {

	module.exports = __webpack_require__(1);


/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {

	__webpack_require__(2);
	__webpack_require__(3);

	alert('main');


/***/ },
/* 2 */
/***/ function(module, exports) {

	alert('coins');

/***/ },
/* 3 */
/***/ function(module, exports, __webpack_require__) {

	__webpack_require__(2);
	alert('helpers');


/***/ }
/******/ ]);

What about Grunt, Gulp…?

Differences:

  • not using global scope
  • not duplicating code while importing module

Why should we use webpack?

  • code splitting
  • performance
  • optimazations
  • loaders
  • plugins

Code splitting

For big web apps it’s not efficient to put all code into a single file

Optimazation

  • Deduplication

  • Minimize

  • Chunks

What we need?

npm install -g webpack

webpack ./entry.js bundle.js

Ways of configuring

module.exports = {
    entry: "./entry.js",
    output: {
        path: __dirname,
        filename: "bundle.js"
    },
    module: {
        loaders: [
            { test: /\.css$/, loader: "style!css" }
        ]
    }
};

webpack config file

Pure CLI

webpack <entry> <output>

Webpack CLI options

Development shortcut -d

Equals to --debug --devtool source-map --output-pathinfo

Production shortcut -p

Equals to --optimize-minimize --optimize-occurence-order

Watch mode --watch

Watches all dependencies and recompile on change.

Display options

  • --progress
  • --json
  • --no-color
  • --sort-modules-by, --sort-chunks-by, --sort-assets-by
  • --display-chunks
  • --display-reasons
  • --display-error-details
  • --display-modules
  • --display-exclude

Options

Entry

entry: './src/index.js'
  • String - single entry point
entry: ['./src/index.js', './src/googeAnalytics.js']
  • Array - multiple independent entry points, one result bundle

Entry

entry: {
    'main': './src/index.js',
    'unsupported': './src/unsupported.js'
}
  • Object - multiple independent entry points & multiple result bundles

Output

output: {
    path: __dirname + '/dist'
}
  • Path - bundle goes here

Output

output: {
    filename: 'main.js'
}
  • Filename - bundle name
output: {
    filename: '[name]_bundle.js'
}

For multiple bundles:

entry: {
    'main': './src/index.js',
    'unsupported': './src/unsupported.js'
}

Results in 2 files: main_bundle.js & unsupported_bundle.js

Output

output: {
    publicPath: 'http://myCdn.com/'
}
  • Publicpath - updates URLs inside app (usually for production only)
.image {
    background-image: url('./test.jpg');
}
.image {
    background-image: url('http://myCdn.com//test.jpg');
}

Loaders

module: {
 loaders: [{
  <loader configuration here>
 }]
}

Plugins

plugins: [
    new MyPlugin({options: 'some option'}),
    ...
]

Resolve

resolve: {
...
    modules: [
      "node_modules",
      path.resolve(__dirname, "app")
    ]
}
  • Modules - where to look for modules
  • Extensions
resolve: {
...
    extensions: [".js", ".json", ".jsx", ".css"],
}

Webpack dev server

npm i webpack-dev-server
  • Small node.js express server
  •  It uses webpack to compile assets in-memory

Installation

Running

webpack-dev-server --content-base /build

Inline and iframe mode

HOT MODULE REPLACEMENT

Proxy

{
  devServer: {
    proxy: {
      '/api': {
        target: 'https://other-server.example.com',
        secure: false
      }
    }
  }
}

Loaders

What's this?

Loaders

How to install new loaders?

Loaders

via NPM

npm install --save-dev babel-loader

Loaders

How to include loaders?

Loaders

loaders: [{ test: /\.js$/, loader: 'babel-loader' }]

Include loaders by config.

module.exports = {
  entry: './main.js',
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.js$/, loader: 'babel-loader' }
    ]
  }
};

Loaders

Include loaders by require.

require("babel-loader!./main.js");
require("!style!css!less!bootstrap/less/bootstrap.less");

Loaders

Loaders examples.

Loaders

Loaders examples.

  •  file-loader
  • css-loader / less-loader / sass-loader
  • style-loader
  • autoprefixer-loader
  • babel-loader

 

Loaders

More loaders.

https://webpack.github.io/docs/list-of-loaders.html​

 

Loaders

Loaders and preloaders.

Loaders

Loaders and preloaders.

module.exports = {
  entry: './main.js',
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.js$/, loader: 'babel-loader' }
    ],
    preLoaders: [
      { test: /\.js$/, loader: 'babel-hint-loader' }
    ]
  }
};

Loaders

Queue for loaders and preloaders.

  1. preloaders specified in the configuration
  2. loaders specified in the configuration
  3. loaders specified in the request (e.g. require('raw!./file.js'))
  4. postLoaders specified in the configuration

 

Loaders

Other parameters

Loaders

exclude, query

Loaders

exclude, query

module: {
   preLoaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'jshint-loader'

      }
   ],
   loaders: [
     {
       exclude: /node_modules/,
       loader: 'babel-loader',
       query: {
          cacheDirectory: true, 
          presets: ['react', 'es2015'] 
       }
      }
   ]
 }

Loaders

Can I create my the best custom loader?

Loaders

Yes!

Loaders

var _ = require('lodash');
var utils = require('loader-utils');

function processQuery(source, query) {
  if (!_.isUndefined(query.search) && !_.isUndefined(query.replace)) {
    if (!_.isUndefined(query.flags)) {
      query.search = new RegExp(query.search, query.flags);
    }
    source = source.replace(query.search, query.replace);
  }
  return source;
}

module.exports = function (source) {
  this.cacheable();
  var query = utils.parseQuery(this.query);
  if (_.isArray(query.multiple)) {
    query.multiple.forEach(function (subquery) {
      source = processQuery(source, subquery);
    });
  } else {
    source = processQuery(source, query);
  }
  return source;
};

What is it?

Plugin

 Third party code that have access to Webpack chunks

Are they any good?

Plugin

  • Yes. They enable the developers to inspect the chunks and make changes when needed
  • Allows us to modify the files in runtime, so we can adjust them to run in diverse environments
  • They are not that complicated to implement
  • There are numerous different plugins available

Some plugins - You’ve probably used one of them before

  • html-webpack-plugin // Helps in the creating of HTML files with hash support
  • webpack-spritesmith // Webpack plugin that converts set of images into a spritesheet and SASS/LESS/Stylus mixins
  • webpack-hot-middleware // Receive updated bundles from Webpack server

But watch out for outdated plugins!

How to use a plugin

First require your plugin

var SpritesmithPlugin = require('webpack-spritesmith');

Then add it to plugins array

    // ...
    plugins: [
        new SpritesmithPlugin({
            src: {
                cwd: path.resolve(__dirname, 'src/ico'),
                glob: '*.png'
            },
            target: {
                image: path.resolve(__dirname, 'src/spritesmith-generated/sprite.png'),
                css: path.resolve(__dirname, 'src/spritesmith-generated/sprite.styl')
            },
            apiOptions: {
                cssImageRef: "~sprite.png"
            }
        })
    ]
    // ...

Can I write my own plugins?

Important things

  • Compiler
  • Compilation
  • Prototype.apply
function HelloWorldPlugin(options) {
  // Setup the plugin instance with options...
}

HelloWorldPlugin.prototype.apply = function(compiler) {
  compiler.plugin('done', function() {
    console.log('Hello World!'); 
  });
};

module.exports = HelloWorldPlugin;

Example

function MyPlugin() {}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {

    // Explore each chunk (build output):
    compilation.chunks.forEach(function(chunk) {
      // Explore each module within the chunk (built inputs):
      chunk.modules.forEach(function(module) {
        // Explore each source file path that was included into the module:
        module.fileDependencies.forEach(function(filepath) {
          // we have learned a lot about the source structure now...
        });
      });

      // Explore each asset filename generated by the chunk:
      chunk.files.forEach(function(filename) {
        // Get the asset source for each file generated by the chunk:
        var source = compilation.assets[filename].source();
      });
    });

    callback();
  });
};

module.exports = MyPlugin;

example

Common usecases

Problem:

I want to Uglify my code

new webpack.optimize.UglifyJsPlugin({
    compress: true,
    mangle: true,
    comments: false
})

Problem:

Styles are bundled with all other assets. I want to have a separate styles.css file.

var ExtractTextPlugin = require("extract-text-webpack-plugin");
...

module: {
    loaders: [
        { test: /\.css$/, loader: ExtractTextPlugin.extract({
            loader: "css-loader"
        }) }
    ]
},
plugins: [
    new ExtractTextPlugin("styles.css")
]

Problem:

I have different build configurations for local development and for production. For each one of them, I want to include a different file every time I require 'config' module in my JS code.

resolve: {
	alias: { config: "/absolute/path/to/config.js" }
}
require("config"); -> /absolute/path/to/config.js
resolve: {
	alias: { config: "/some/dir" }
}
require("config/file.js") -> /some/dir/file.js

Problem:

I have different build configurations for local development and for production. For each one of them, I want to have other global variables defined.

new webpack.DefinePlugin({
    PRODUCTION: JSON.stringify(true),
    ENABLE_LOGGING: JSON.stringify(true)
})
if (ENABLE_LOGGING) {
    console.log('Log some info here.')
}

Problem:

I have multiple output files produced from couple of entry points, but there are some modules that are used in more than 1 output bundle. How can I avoid duplication of this module?

plugins: [
    new CommonsChunkPlugin("common.js")
]
plugins: [
    new CommonsChunkPlugin({
        filename: "common.js",
        minSize: 50000 // bytes,
        minChunks: 3
})
]
Made with Slides.com