/webpack-demos

a collection of simple demos of Webpack

Primary LanguageJavaScript

This repo is a collection of simple demos of Webpack.

These demos are purposely written in a simple and clear style. You will find no difficulty in following them to learn the powerful tool.

How to use

First, install Webpack and webpack-dev-server globally.

$ npm i -g webpack webpack-dev-server

Then, clone the repo and install the dependencies.

$ git clone git@github.com:ruanyf/webpack-demos.git
$ cd webpack-demos
$ npm install

Now, play with the source files under the repo's demo* directories.

$ cd demo01
$ webpack-dev-server

Visit http://127.0.0.1:8080 with your browser.

Foreword: What is Webpack

Webpack is a front-end build systems like Grunt and Gulp.

It can be used as a module bundler similar to Browserify, and do much more.

$ browserify main.js > bundle.js
# be equivalent to
$ webpack main.js bundle.js

Its configuration file is webpack.config.js.

// webpack.config.js
module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};

After having webpack.config.js, you can invoke Webpack without any arguments.

$ webpack

Some command-line options you should know.

  • webpack – for building once for development
  • webpack -p – for building once for production (minification)
  • webpack --watch – for continuous incremental build
  • webpack -d – to include source maps
  • webpack --colors – for making things pretty

To produce a production ready application, you could write scripts field in your package.json file as following.

// package.json
{
  // ...
  "scripts": {
    "dev": "webpack-dev-server --devtool eval --progress --colors",
    "deploy": "NODE_ENV=production webpack -p"
  },
  // ...
}

Index

  1. Entry file
  2. Multiple entry files
  3. Babel-loader
  4. CSS-loader
  5. Image loader
  6. CSS Module
  7. UglifyJs Plugin
  8. Environment flags
  9. Code splitting
  10. Code splitting with bundle-loader
  11. Common chunk
  12. Vendor chunk
  13. Exposing Global Variables
  14. React hot loader
  15. React router

Demo01: Entry file (source)

Entry file is a file which Webpack will read to build bundle.js.

For example, main.js is an entry file.

// main.js
document.write('<h1>Hello World</h1>');

index.html

<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

Webpack follows webpack.config.js to build bundle.js.

// webpack.config.js
module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};

Launch the server, visit http://127.0.0.1:8080 .

$ webpack-dev-server

Demo02: Multiple entry files (source)

Multiple entry files are allowed. It is useful for a multi-page app.

// main1.js
document.write('<h1>Hello World</h1>');

// main2.js
document.write('<h2>Hello Webpack</h2>');

index.html

<html>
  <body>
    <script src="bundle1.js"></script>
    <script src="bundle2.js"></script>
  </body>
</html>

webpack.config.js

module.exports = {
  entry: {
    bundle1: './main1.js',
    bundle2: './main2.js'
  },
  output: {
    filename: '[name].js'
  }
};

Demo03: Babel-loader (source)

Loaders are preprocessors which transform a resource file of your app. For example, Babel-loader can transform JSX/ES6 file into JS file.

main.jsx is a JSX file.

var React = require('react');

React.render(
  <h1>Hello, world!</h1>,
  document.body
);

index.html

<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      { test: /\.js[x]?$/, exclude: /node_modules/, loader: 'babel-loader' },
    ]
  }
};

In webpack.config.js, module.loaders field is used to assign loaders.

Demo04: CSS-loader (source)

Webpack allows you to require CSS in JS file, then preprocessed CSS file with CSS-loader.

main.js

require('./app.css');

app.css

body {
  background-color: blue;
}

index.html

<html>
  <head>
    <script type="text/javascript" src="bundle.js"></script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

webpack.config.js

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

Attention, you have to use two loaders to transform CSS file. First is CSS-loader to read CSS file, and another is Style-loader to insert Style tag into HTML page. Different loaders are linked by exclamation mark(!).

After launching the server, index.html will have inline style.

<head>
  <script type="text/javascript" src="bundle.js"></script>
  <style type="text/css">
    body {
      background-color: blue;
    }
  </style>
</head>

Demo05: Image loader (source)

Webpack could also require images in JS files.

main.js

var img1 = document.createElement("img");
img1.src = require("./small.png");
document.body.appendChild(img1);

var img2 = document.createElement("img");
img2.src = require("./big.png");
document.body.appendChild(img2);

index.html

<html>
  <body>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }
    ]
  }
};

url-loader transforms image files. If the image size is smaller than 8192 bytes, it will be transformed into Data URL; otherwise, it will be transformed into normal URL. As you see, question mark(?) is be used to pass parameters into loaders.

After launching the server, small.png and big.png will have the following URLs.

<img src="data:image/png;base64,iVBOR...uQmCC">
<img src="4853ca667a2b8b8844eb2693ac1b2578.png">

Demo06: CSS Module (source)

css-loader?modules (the query parameter modules) enables the CSS Modules spec.

It means your module's CSS is local scoped CSS by default. You can switch it off with :global(...) for selectors and/or rules. (more info)

index.html

<html>
<body>
  <h1 class="h1">Hello World</h1>
  <h2 class="h2">Hello Webpack</h2>
  <div id="example"></div>
  <script src="./bundle.js"></script>
</body>
</html>

app.css

.h1 {
  color:red;
}

:global(.h2) {
  color: blue;
}

main.jsx

var React = require('react');
var style = require('./app.css');

React.render(
  <div>
    <h1 className={style.h1}>Hello World</h1>
    <h2 className="h2">Hello Webpack</h2>
  </div>,
  document.getElementById('example')
);

webpack.config.js

module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      { test: /\.js[x]?$/, exclude: /node_modules/, loader: 'babel-loader' },
      { test: /\.css$/, loader: 'style-loader!css-loader?modules' }
    ]
  }
};

Launch the server.

$ webpack-dev-server

Visit http://127.0.0.1:8080 , you'll find that only second h1 is red, because its CSS is local scoped, and both h2 is blue, because its CSS is global scoped.

Demo07: UglifyJs Plugin (source)

Webpack has a plugin system to expand its functions. For example, UglifyJs Plugin will minify JS codes.

main.js

var longVariableName = 'Hello';
longVariableName += ' World';
document.write('<h1>' + longVariableName + '</h1>');

index.html

<html>
<body>
  <script src="bundle.js"></script>
</boby>
</html>

webpack.config.js

var webpack = require('webpack');
var uglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new uglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
};

After launching the server, main.js will be minified into following.

var o="Hello";o+=" World",document.write("<h1>"+o+"</h1>")

Demo08: Environment flags (source)

You can enable some codes only in development environment with environment flags.

main.js

document.write('<h1>Hello World</h1>');

if (__DEV__) {
  document.write(new Date());
}

index.html

<html>
<body>
  <script src="bundle.js"></script>
</body>
</html>

webpack.config.js

var webpack = require('webpack');

var devFlagPlugin = new webpack.DefinePlugin({
  __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
});

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [devFlagPlugin]
};

Now pass environment variable into webpack.

$ env DEBUG=true webpack-dev-server

Demo09: Code splitting (source)

For big web apps it’s not efficient to put all code into a single file, Webpack allows you to split them into several chunks. Especially if some blocks of code are only required under some circumstances, these chunks could be loaded on demand.

At first, you use require.ensure to define a split point. (official document)

// main.js
require.ensure(['./a'], function(require) {
  var content = require('./a');
  document.open();
  document.write('<h1>' + content + '</h1>');
  document.close();
});

require.ensure tells Webpack that ./a.js should be separated from bundle.js and built into a single chunk file.

// a.js
module.exports = 'Hello World';

Now Webpack takes care of the dependencies, output files and runtime stuff. You don't have to put any redundancy into your index.html and webpack.config.js.

<html>
  <body>
    <script src="bundle.js"></script>
  <body>
</html>

webpack.config.js

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js'
  }
};

Launch the server.

$ web-dev-server

On the surface, you won't feel any differences. However, Webpack actually builds main.js and a.js into different chunks(bundle.js and 1.bundle.js), and loads 1.bundle.js from bundle.js when on demand.

Demo10: Code splitting with bundle-loader (source)

Another way of code splitting is using bundle-loader.

// main.js
var load = require('bundle-loader!./a.js');

load(function(file) {
  document.open();
  document.write('<h1>' + file + '</h1>');
  document.close();
});

require('bundle-loader!./a.js') tells Webpack to load a.js from another chunk.

Now Webpack will build main.js into a.js, and a.js into 1.bundle.js.

Demo11: Common chunk (source)

When multi scripts have common chunks, you can extract the common part into a separate file with CommonsChunkPlugin.

// main1.jsx
var React = require('react');
React.render(
  <h1>Hello World</h1>,
  document.getElementById('a')
);

// main2.jsx
var React = require('react');
React.render(
  <h2>Hello Webpack</h2>,
  document.getElementById('b')
);

index.html

<html>
  <body>
    <div id="a"></div>
    <div id="b"></div>
    <script src="init.js"></script>
    <script src="bundle1.js"></script>
    <script src="bundle2.js"></script>
  </body>
</html>

webpack.config.js

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
  entry: {
    bundle1: './main1.jsx',
    bundle2: './main2.jsx'
  },
  output: {
    filename: '[name].js'
  },
  module: {
    loaders:[
      { test: /\.js[x]?$/, exclude: /node_modules/, loader: 'babel-loader' },
    ]
  },
  plugins: [
    new CommonsChunkPlugin('init.js')
  ]
}

Demo12: Vendor chunk (source)

You can also extract the vendor libraries from a script into a separate file with CommonsChunkPlugin.

main.js

var $ = require('jquery');
$('h1').text('Hello World');

index.html

<html>
  <body>
    <h1></h1>
    <script src="vendor.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>

webpack.config.js

var webpack = require('webpack');

module.exports = {
  entry: {
    app: './main.js',
    vendor: ['jquery'],
  },
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin(/* chunkName= */'vendor', /* filename= */'vendor.js')
  ]
};

If you want a module available as variable in every module, such as making $ and jQuery available in every module without writing require("jquery"). You should use ProvidePlugin (Official doc).

// main.js
$('h1').text('Hello World');


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

module.exports = {
  entry: {
    app: './main.js'
  },
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
      "window.jQuery": "jquery"
    })
  ]
};

Demo13: Exposing global variables (source)

If you want to use some global variables, and don't want to includes them in the Webpack bundle, you can enable externals field in webpack.config.js (official document).

For example, we have a data.js.

var data = 'Hello World';

We can expose data as a global variable.

// webpack.config.js
module.exports = {
  entry: './main.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders:[
      { test: /\.js[x]?$/, exclude: /node_modules/, loader: 'babel-loader' },
    ]
  },
  externals: {
    // require('data') is external and available
    //  on the global var data
    'data': 'data'
  }
};

Now, you require data as a module variable in your script. but it actually is a global variable.

// main.jsx
var data = require('data');
var React = require('react');

React.render(
  <h1>{data}</h1>,
  document.body
);

Demo14: React hot loader (source)

This demo is copied from React hot boilerplate.

React Hot Loader is a plugin for Webpack that allows instantaneous live refresh without losing state while editing React components.

Because we use global webpack-dev-server, you have to install some modules globally as well to run this demo.

$ npm i -g react-hot-loader react babel-loader

Then run webpack-dev-server.

$ webpack-dev-server --progress --hot

Now you should see 'Hello World' in your browser. --hot option tells webpack-dev-server to replace a component without a full page reload when the component source changed.

Don't close the server. Open a new terminal to edit App.js, and modify 'Hello World' into 'Hello Webpack'. Save it, and see what happened in the browser.

App.js

import React, { Component } from 'react';

export default class App extends Component {
  render() {
    return (
      <h1>Hello World</h1>
    );
  }
}

index.js

import React from 'react';
import App from './App';

React.render(<App />, document.getElementById('root'));

index.html

<html>
  <body>
    <div id='root'></div>
    <script src="/static/bundle.js"></script>
  </body>
</html>

webpack.config.js

var webpack = require('webpack');
var path = require('path');

module.exports = {
  entry: [
    'webpack-dev-server/client?http://localhost:8080',
    'webpack/hot/only-dev-server',
    './index.js'
  ],
  output: {
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  module: {
    loaders: [{
      test: /\.jsx?$/,
      loaders: ['react-hot-loader', 'babel-loader'],
      include: path.join(__dirname, '.')
    }]
  }
};

Demo15: React router (source)

This demo uses webpack to build React-router's official example.

Let's imagine a little app with a dashboard, inbox, and calendar.

+---------------------------------------------------------+
| +---------+ +-------+ +--------+                        |
| |Dashboard| | Inbox | |Calendar|      Logged in as Jane |
| +---------+ +-------+ +--------+                        |
+---------------------------------------------------------+
|                                                         |
|                        Dashboard                        |
|                                                         |
|                                                         |
|   +---------------------+    +----------------------+   |
|   |                     |    |                      |   |
|   | +              +    |    +--------->            |   |
|   | |              |    |    |                      |   |
|   | |   +          |    |    +------------->        |   |
|   | |   |    +     |    |    |                      |   |
|   | |   |    |     |    |    |                      |   |
|   +-+---+----+-----+----+    +----------------------+   |
|                                                         |
+---------------------------------------------------------+
$ webpack-dev-server

Useful links

License

MIT