webpack



webpack

0 0


webpack


On Github JohnGorter / webpack

Webpack

Bundeling js files

Agenda

  • Introduction
  • Basic setup
  • Webpack merge
  • Loaders
  • Plugins
  • Code Splitting
  • Stylesheets
  • Libraries and externals
  • Dev tools
  • Hot loading
  • Using webpack with Angular2

Rules of engagement

  • Training hours
  • Lunches
  • Phones
  • Evaluation

Have fun!

Webpack introduction

What is webpack

What is Webpack

From the docs:

Webpack is a popular module bundler, a tool for bundling application source code in convenient chunks and for loading that code from a server into a browser.

Why do we need Webpack

Websites and applications contain

  • more and more on JavaScipt

We need

  • Modularisation

This results in larger amounts of code on the client side. Generally speaking, utilising a module system should enhance not only the development but also the user experience. If you happen to be working with a large codebase or just researching, you will no doubt understand it is critical to keep your code organised. One of the best ways to handle the organisation of JavaScript it to break your code into modules. When it comes to module systems there are a few popular ones which you might of either used or heard about like Browserify or RequireJS. Both of these are extremely helpful and do a great job but webpack does some extra radness.

Why do we need Webpack

Webpack enables professional and maintainable JavaScript programming

Webpack enables us to write JavaScript code into maintainable modules while taking care of packaging and bundeling it for runtime use.

Why do we need Webpack

Webpack supports:

  • module traversing and bundeling, you can organise code into modules
  • on demand loading, reducing the initial load time of application
  • module hashing, make chunks cache friendly
  • plugins, making it super extensible
  • loaders, it can build and bundle CSS, compile-to-JS languages (CoffeeScript), images and more

What does Webpack do

  • Webpack roams over your application source code, looking for import statements, building a dependency graph, and emitting one (or more) bundles.

  • With plugin "loaders" Webpack can preprocess and minify different non-JavaScript files such as TypeScript, SASS, and LESS files.

What is a bundle

A bundle is a JavaScript file that incorporate assets that belong together and should be served to the client in a response to a single file request.

A bundle can include JavaScript, CSS styles, HTML, and almost any other kind of file.

picture overview

How does Webpack does it

Webpack works with a JavaScript configuration file(s)

webpack.config.js

You just have to call it:

webpack

Basic setup

Defining the least supported setup

prerequisites

npm install webpack --save

Test webpack

  • Create a javascript file, test.js
  • Run webpack command
      webpack ./test.js bundle.js
    
  • Check the output, should be like:
      Version: webpack 1.12.11
      Time: 51ms
      Asset     Size  Chunks             Chunk Names
      bundle.js  1.42 kB       0  [emitted]  main
      chunk    {0} bundle.js (main) 28 bytes [rendered]
      [0] ./test.js 28 bytes {0} [built]
    
  • And of course there should be a generated bundle.js

Labtime

Create the inital setup and test it

Using external configuration

Webpack can be configured to start with a setup javascript file

  • add a webpack.config.js (name is required)
  • start webpack
  • start webpack --config .js>

The config file is not JSON but a JavaScript file so it can contain executable script. Lets look at an example:

sample webpack.config.js

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

Configuration options

Some handy configuration options to remember

webpack          // for building once for development
webpack -p       // for building once for production (minification)
webpack --watch  // for continuous incremental build
webpack -d       // for including source maps
webpack --colors // for making things pretty

Labtime

Create the config file setup and test it

Webpack merge

Split and merge configurations

Config Splitting

More complex scenario's require different setups:

  • Development environments needs debugging info
  • Test environments run tests afterwards
  • Production environments uglify and minify JavaScript

prerequisites

  • Webpack merge
npm i webpack-merge --save-dev

Configs are JavaScript files, so we can code in them

API

var output = merge(object1, object2, object3, ...);

// smarter merging for loaders, see below 
var output = merge.smart(object1, object2, object3, ...);

Smart Merging of Loaders

Webpack-merge tries to be smart about merging loaders when merge smart is used.

Loaders with matching tests will be merged into a single loader value.

More info: https://www.npmjs.com/package/webpack-merge

Merging configurations

Merging configurations in

  • a single file
  • split configuration files

Single file merging

Use switches to set different settings for different environments, export the merge result of the common and specific configurations

Example

Single file merging example

webpack.config.js

const merge = require('webpack-merge');
const common = {
  entry: { app: 'app' },
  output: {
    path: 'build',
    filename: '[name].js'
  },
  plugins: [ ... ]
};
// Detect how npm is run and branch based on that
// u can use npm run 'script' to set this value
switch(process.env.npm_lifecycle_event) {
  case 'build':
    module.exports = merge(common, { /*specifics*/});
    break;
  default:
    module.exports = merge(common, { /*specifics*/});
}

Multiple configuration file merging

Use a common setting for the basic shared configuration

  • webpack.common.js

Use a configuration file for each specific settings, for example:

  • webpack.dev.js
  • webpack.prod.js
  • webpack.test.js

Merge the common configuration into the specific configuration. Example:

Multiple configuration file merging example

webpack.config.js
module.exports = require('./config/webpack.dev.js');

Define common webpack config

webpack.common.js

var webpack = require('webpack');

module.exports = {
  entry: {
    'polyfills': './src/polyfills.ts',
    'vendor': './src/vendor.ts',
    'app': './src/main.ts'
  },

  /* other settings and common config */
  resolve: { ... },
  module: { ... },
  plugins: [ ... ]
};

define environment config

webpack.dev.js

var webpackMerge = require('webpack-merge');
var commonConfig = require('./webpack.common.js');

module.exports = webpackMerge(commonConfig, {
  devtool: 'cheap-module-eval-source-map',

  output: {
    path: helpers.root('dist'),
    publicPath: 'http://localhost:8080/',
    filename: '[name].js',
    chunkFilename: '[id].chunk.js'
  },

  plugins: [ ... ],
  devServer: { ... }
});

Labtime

Merge multiple configurations

Validate webpack

Using webpack-validator

What is webpack-validator

To make it easier to develop our configuration, we can integrate a tool known as webpack-validator to our project. It will validate the configuration against a schema and warn if we are trying to do something not sensible. This takes some pain out of learning and using Webpack.

Prerequisites

Install:

npm i webpack-validator --save-dev

Use it

Integrating it to our project is straight-forward:

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const merge = require('webpack-merge');
const validate = require('webpack-validator');

...

module.exports = validate(config);

Test it

If you break your Webpack configuration somehow after doing this, the validator will likely notice and give you a nice validation error to fix.

Labtime

Test a configuration with webpack-validator, you're up next bro

Loaders

File preprocessors

What are loaders

Loaders allow you to preprocess files as you require() or “load” them. Loaders can transform files from a different language like

  • CoffeeScript to JavaScript
  • inline images as data URLs

Loaders even allow you to do things like require() css files right in your JavaScript!

How to use

To tell Webpack to transform a module with a loader, you can specify the loader in the module request

var moduleWithOneLoader = require("my-loader!./my-awesome-module");

Or with a relative path

require("./loaders/my-loader!./my-awesome-module");

List of loaders

A lot of prebuilt loaders in the following categories

  • Basic -> json, imports, val, raw, etc
  • Packaging -> image, , extract, file, modernizr, etc
  • Dialects -> babel, coffee, typescript, sweetjs, etc
  • Templating -> html, dom, riot, dust, markdown, etc
  • Styling -> bootstrap-sass, css, less, rework, etc
  • Translation -> po, gettext, amdi18n-loader, etc
  • Support -> mocha, falafel, csslint, standard, etc

complete list

https://github.com/webpack/docs/wiki/list-of-loaders

Use a loader

  • Choose a loader from the list, for example typescript-loader
  • Install it using
    npm install typescript-loader --save-dev
    
  • use it in your config
    module: {
      loaders: [
        {
          test: /\.ts$/,
          loader: 'typescript-loader'
        }
      ]
    },
    

Use a loader(2)

  • Choose a loader from the list, for example tojson-loader
  • Install it using
    npm install typescript-loader --save-dev
    
  • use it in your config
    // webpack.config.js
    module.exports = {
    module: {
      loaders: [
        {
          // Use *.json.js extension
          test: /\.json\.js/,
          loader: 'tojson'
        }
      ]
    }
    },
    

Use a loader(3)

  • Use it
// file: data.json.js
var fs = require('fs')
var readme = fs.readFileSync(__dirname + '/../../Readme.md', 'utf8')
readme = readme.split('\n')[0] // (just grab header for demo)

// any other dependencies that are only used in here won't be included in bundle
var tape = require('tape') // some random dependency

// whatever the value of module.exports is will be serialised to JSON
module.exports = {
  readme: readme,
  tape: tape, // tape happens to be a function so it won't serialise.
  random: Math.random(), // will be fixed to whatever value is generated at compile-time
}
// file: index.js
console.log(require('./data.json.js'))

Use a loader(4)

  • Outputs
    ...
    /******/ ([
    /* 0 */
    /***/ function(module, exports, __webpack_require__) {
      console.log(__webpack_require__(1))
    /***/ },
    /* 1 */
    /***/ function(module, exports) {
      module.exports = {"readme":"# tojson-loader","random":0.5418716457206756}
    /***/ }
    /******/ ]);
    

Chaining loaders

Loaders can be also be chained together

require("style-loader!css-loader!less-loader!./my-styles.less");
  • Helpful for applying multiple transformations
  • Applied right to left (from the file, back)

Parameters

Loaders can accept query parameters

require("loader?with=parameter!./file");
  • Format of the query string is up to the loader
  • Most loaders support the traditional query string format

Loaders by config

We do not want to specify loaders in each module request

  • brittle and repetitive
  • avoid build specific syntax to the code

Apply loaders to different file types in config!

{
    module: {
        loaders: [
            { test: /\.coffee$/, loader: "coffee-loader" }
        ],
        preLoaders: [
            { test: /\.coffee$/, loader: "coffee-hint-loader" }
        ]
    }
};

Loader order

For each file, loaders are executed in the following order

preloaders from config loaders from config loaders specified in request (e.g. require('raw!./file.js')) postLoaders from config

Override loader order

You can override the config loader order in the module request to suit special cases.

Disable configured preLoaders

require("!raw!./script.coffee")

Disable all loaders specified in the configuration

require("!!raw!./script.coffee")

Disable configured preLoaders and loaders but not the postLoaders

require("-!raw!./script.coffee")

Labtime

Using loaders in webpack

Custom loaders

You can write your own loaders

What is a loader

A loader is just a file that exports a function. The compiler calls this function and passes the result of the previous loader or the resource file into it

The -this- context of the function is filled-in with useful methods that allow:

  • change its invocation style to async
  • get query parameter

What is a loader(2)

  • The first loader is passed one argument: the content of the resource file.
  • The compiler expects a result from the last loader.
  • The result should be String or Buffer (gets converted to string), representing the JavaScript source code

An optional SourceMap result (as JSON object) may also be passed.

What is a loader(3)

  • A single result can be returned in sync mode.
  • Multiple results
    • In async mode this.async() must be called.
    • this.callback must be called.

Errors can be thrown in sync mode or the this.callback can be called with the error.

Example sync loader

Sync loader returns results synchronously

module.exports = function(content) {
    return someSyncOperation(content);
};

Example Async loader

Async loader returns value through callback

module.exports = function(content) {
    var callback = this.async();
    if(!callback) return someSyncOperation(content);
    someAsyncOperation(content, function(err, result) {
        if(err) return callback(err);
        callback(null, result);
    });
};

Example Raw loader

  • By default the resource file is passed as String(utf-8) to the loader

  • By setting raw to true the loader is passed the raw Buffer

  • Every loader is allowed to deliver its result as String or as Buffer

  • The compiler converts them between loaders

module.exports = function(content) {
    assert(content instanceof Buffer);
    return someSyncOperation(content);
    // return value can be a Buffer too
    // This is also allowed if loader is not "raw"
};
module.exports.raw = true;

Pitching loader

  • Loaders are called from right to left
  • In some cases loaders do not care about the results of the previous loader or the resource
  • Pitch method on loaders is called from left to right before loaders are called

If a loader returns result in the pitch method the process skips the remaining loaders continuing with the calls to the more left loaders

  • Data can be passed between pitch and normal call.

Pitching loader(2)

It's like the two phases of event bubbling...

a!b!c!resource
  • pitch a
    • pitch b
      • pitch c
        • read file resource (adds resource to dependencies)
      • run c
    • run b
  • run a

Pitching loader(3)

When a loader return something in the pitch phase the process continues with the normal phase of the next loader...

  • pitch a
    • pitch b (returns something)
  • run a

Example pitching loader

module.exports = function(contnt) {
    return someSyncOp(contnt, this.data.value);
};
module.exports.pitch = function(remainReq, preceedReq, data) {
    if(someCondition()) {
        // fast exit
        return "module.exports = require(" + JSON.stringify("-!" + remainReq) + ");";
    }
    data.value = 42;
};

Labtime

Writing a custom loader in webpack

Hot Loading

Hot Module Replacement

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.

Prerequirements

Using Plugins: http://webpack.github.io/docs/using-plugins.html Code Splitting: http://webpack.github.io/docs/code-splitting.html webpack-dev-server: http://webpack.github.io/docs/webpack-dev-server.html

How does it work?

Webpacks adds a small HMR runtime to the bundle, during the build process, that runs inside your app. When the build completes, Webpack does not exit but stays active, watching the source files for changes. If Webpack detects a source file change, it rebuilds only the changed module(s).

Depending on the settings, Webpack will either send a signal to the HMR runtime, or the HMR runtime will poll webpack for changes. Either way, the changed module is sent to the HMR runtime which then tries to apply the hot update. First it checks whether the updated module can self-accept. If not, it checks those modules that have required the updated module.

If these too do not accept the update, it bubbles up another level, to the modules that required the modules that required the changed module. This bubbling-up will continue until either the update is accepted, or the app entry point is reached, in which case the hot update fails.

From the app view

The app code asks the HMR runtime to check for updates. The HMR runtime downloads the updates (async) and tells the app code that an update is available. The app code asks the HMR runtime to apply updates. The HMR runtime applies the update (sync). The app code may or may not require user interaction in this process (you decide).

From the compiler (webpack) view

Compiler needs to emit the "Update", which contains:

  • the update manifest (json)
    • contains the new compilation hash and a list of all update chunks
  • one or multiple update chunks (js)
    • contains code for all updated modules in this chunk (or a flag if a module was removed).

In addition to the normal assets, the compiler needs to emit the "Update" to allow updating the previous version to the current version.

The "Update" contains two parts:

  • the update manifest (json)
  • one or multiple update chunks (js)

The manifest contains the new compilation hash and a list of all update chunks (2.).

The update chunks contains code for all updated modules in this chunk (or a flag if a module was removed).

The compiler also makes sure that module and chunk ids are consistent between these builds. It uses a "records" json file to store them between builds (or it stores them in memory).

Hot Loading

Hot Module Replacement

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.

Prerequirements

Using Plugins: http://webpack.github.io/docs/using-plugins.html Code Splitting: http://webpack.github.io/docs/code-splitting.html webpack-dev-server: http://webpack.github.io/docs/webpack-dev-server.html

How does it work?

Webpacks adds a small HMR runtime to the bundle, during the build process, that runs inside your app. When the build completes, Webpack does not exit but stays active, watching the source files for changes. If Webpack detects a source file change, it rebuilds only the changed module(s).

Depending on the settings, Webpack will either send a signal to the HMR runtime, or the HMR runtime will poll webpack for changes. Either way, the changed module is sent to the HMR runtime which then tries to apply the hot update. First it checks whether the updated module can self-accept. If not, it checks those modules that have required the updated module.

If these too do not accept the update, it bubbles up another level, to the modules that required the modules that required the changed module. This bubbling-up will continue until either the update is accepted, or the app entry point is reached, in which case the hot update fails.

From the app view

The app code asks the HMR runtime to check for updates. The HMR runtime downloads the updates (async) and tells the app code that an update is available. The app code asks the HMR runtime to apply updates. The HMR runtime applies the update (sync). The app code may or may not require user interaction in this process (you decide).

From the compiler (webpack) view

Compiler needs to emit the "Update", which contains:

  • the update manifest (json)
    • contains the new compilation hash and a list of all update chunks
  • one or multiple update chunks (js)
    • contains code for all updated modules in this chunk (or a flag if a module was removed).

In addition to the normal assets, the compiler needs to emit the "Update" to allow updating the previous version to the current version.

The "Update" contains two parts:

  • the update manifest (json)
  • one or multiple update chunks (js)

The manifest contains the new compilation hash and a list of all update chunks (2.).

The update chunks contains code for all updated modules in this chunk (or a flag if a module was removed).

The compiler also makes sure that module and chunk ids are consistent between these builds. It uses a "records" json file to store them between builds (or it stores them in memory).

Hello world

sdsds

Webpack Bundeling js files