Work In Progress

While webpack is some kind of mature, this presentation is wip!

Hi, I'm a module system for the browser.

You like to get know me? I'll tell you more.

You can navigate through this website with

Yeah. Ready to get to know webpack: Go right →!

Customize the content

Answer these few questions so
I can present you relevant stuff only:

(or just skip them, than I'll show you all stuff)

Content

Todays Web Apps: Motivation

Today websites are evolving into web apps:

→ Much code on client side!

Module system

A big code base need to be organized!

Module systems offer mechanism to split your code base into modules.

Module system styles

Using a module system is common, so there is a standard. But of course there is not only one standard. There are multiple different standards:

<script>-tag style

<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="libaryA.js"></script>
<script src="module3.js"></script>

Modules export a interface to the global object, i. e. window.

Common problems:

CommonJS: synchron require

This is the style you use in node.js on server side.

require("module"); require("../file.js");

Pro

Contra

AMD: asynchronous require

Asynchronous Module Definition

Other module systems had problems with the synchronous require and introduced a asynchronous require:

require(["module", "../file.js"], function(modules, file) { /*...*/ });
define("mymodule", ["dep1", "dep2"], function(d1, d2) { /*...*/ });

Pro

Contra

Existing module systems

CommonJS

As you know browserify, I'll compare it to me on a later slide.

AMD

You know require.js, which will be compared to me later.

Transferring. Simple concatination?

Modules should be executed on the client, so they must be transferred.

There are two extremas how to split requests/files to modules:

Both are used in the wild (preferable the second one), but both are suboptimal:

Transferring. A flexible solution

A more flexible transferring is better.

→ Chunked concatination

Note: The idea is from Google's GWT. A Google developer also recently implemented a similar system for AMD-style modules: google/module-server, but it depends on a server-side component.

I've some slides with more details on my output format. Use the down array (↓) to navigate to them or use the DETAILS button on the right side.

Existing output formats

Other module systems/bundler emit much code into the output file for path resolution logic:

i. e. modules-webmake

// some lines of the modules system
// many lines of the path resolution logic
{
	"folder": {
		"fileA": function of module A,
		"fileB": function of module B
	},
	"fileC": function of module C
}
("./fileC") // Startup

Existing output formats (Con't)

And a module looks like this:

function(exports, module, require) {
	// The file
	module.exports = require("./folder/fileA");
}

This format cause some trouble:

  • Your path names are in the output file.
  • The client need to do the module resolution.
  • Overhead though string like "./folder/fileA".
  • Code for the path resolution must be emitted into the output file.

I can do better.

How can I do better?

  • I don't want the client to do the path resolution and do it while compiling.
  • I don't want to expose path information and replace the strings with small numbers.
// some lines of the modules system
{
	0: function of module C,
	1: function of module A,
	2: function of module B
}

with function of module:

function(exports, module, require) {
	// The file
	module.exports = require(0);
}

I ever start on module 0 to save the startup call.

My output format

My output format make the client-side really simple. On the next slide I include the complete module system, because it's so small that it fits on a slide ;)

Pro

  • Smaller, Faster, Simplier
  • No paths exposed. No path resolution on client side.
  • Less overhead.

Contra

  • Complexer compilation.
  • Source is modified (a little bit). Comments? Source Maps?
  • module.id is not longer a string, but a number.
(function(modules) { /* Sorry, no space for comments... */
    var installedModules = {};
    function require(moduleId) {
        if(installedModules[moduleId])
            return installedModules[moduleId].exports;
        var module = installedModules[moduleId] =
            { exports: {}, id: moduleId, loaded: false };
        modules[moduleId].call(null, module, module.exports, require);
        module.loaded = true;
		return module.exports;
    }
    require.e = function requireEnsure(chunkId, callback) {
        callback(require);
    }; // chunk loading, don't care for it now.
    require.modules = modules;
    require.cache = installedModules;
    return require(0);
})({ /* the modules */ })

Split points

"Ok cool, but how can I set split points to use chunked transferring"

I explain only the require.ensure function. You'll find documentation on AMD-style require in the require.js API.

require.ensure(modules: string[], callback: (require) => void)

You'll find an example on the next slide

Code Splitting

var a = require("a");
var b = require("b");
require.ensure(["c"], function(require) {
    require("b").xyz();
    var d = require("d");
});

File/Chunk 1: bundle.js

File/Chunk 2: 1.bundle.js (loaded when calling require.ensure)

Output format with multiple chunks

If you use Code Splitting the first chunk includes some code to add the other chunks on demand.

I change your calls of require.ensure to calls to require.e (shorter is better) with a chunk id to load. A chunk is loaded by adding a <script>-tag (so it's cross-origin) and callback is called by the JSONP-call the chunk contains.

To sum it up, we have:

  • Code to add <script>-tag in initial chunk
  • JSONP callback in initial chunk
  • Other chunks contain only a call to the JSONP function.

Output format of a chunk

webpackJsonp(1 /* chunk id */, {
    3: function(module, exports, require) { /* the module */ },
    4: function(module, exports, require) { /* the module */ },
    6: function(module, exports, require) { /* the module */ },
	/* ... */
})

which minimizes great:

webpackJsonp(1,{3:function(a,b,c){},4:function(a,b,c){},/* ... */})

Contra

  • Mulitple instances of webpack module systems can conflict if they use the same JSONP-callback name. You can specify the callback name as option.

Why only javascript?

Why should I only help you with javascript? There are so much more static resources:

And also:

Why only javascript? (Con't)

I help you!

require("./style.css");

That's cool.

require("./style.less");
require("./template.jade");
require("./image.png");

Even cooler!

You don't need to put your files though some preprocessing manually. I do it for you and it is very easy to use (As you see from the lines above).

Important note for you as developer: Yes you can extend it very easily. You can use your favorite templates/stylesheets/scripts/whatever.

Loaders

"Ok cool, but what is the technique behind it?"

It's called loaders.

A loader...

require("your-loader!./your-file.xyz");
require("./loader.js!./loader2.js!module/with/a/file");

A simple loader

loader.js

module.exports = function(js) {
	return "exports.answer = 42;\n" + js;
}

file.js

exports.test = "test";

Result:

var loadedFile = require("./loader!./file");
assert(loadedFile.test === "test");
assert(loadedFile.answer === 42);

A asynchronous loader

Here is an example for an asynchronous loader accessing the node.js APIs:

// loader.js
var fs = require("fs");
var path = require("path");
module.exports = function(js) {
	var done = this.async();
	var headerPath = path.join(__dirname, "header.js");
	fs.readFile(headerPath, "utf-8", function(err, header) {
		if(err) return done(err);
		done(null, header + js);
	});
}

→ Loader specification

Some loader examples

Here is a small part of this loaders list:

Some of them may be bound to file extensions if you choose to.

Feel free to write your own loaders.

If you open source your own loaders consider adding them to the list.

Dynamic require

So now all is javascript. So I can show you more of mine javascript features.

return require("./template/" + templateName + ".jade");

Module bundlers do a static code analysis to get dependencies before executing. But this code fragment includes a variable (not compile-time constant). Does the static code analysis fails here?

Note: browserify simply ignore the require and hopes that the files are in the bundle. Because in most cases they are not, you need to force including.

Note: require.js TODO check how exactly require.js fails on this.

Context

I handle this cases with a special mechanism I call "context". All files matching the require are included in the bundle and I find the actual referenced file on runtime.

Emitted modules

For each context a context module is emitted, which contains a map of files in the context and code for finding the correct file.

i. e. a require("./template/" + templateName + ".jade") may result in a map like that: (emitted with lookup code in module 22)

{	// string: module id
	"./templateA.jade": 23,
	"./templateB.jade": 24,
	"./more/templateC.jade": 25
}

... and the require converted to:

require(22)("./" + templateName + ".jade")

The files (modules 23 - 25) are included like any require.

Comparison to other module systems

I'm not alone. There are multiple other module systems out there.

Here are the three top points I do better than others:

On the next slide you'll find a comparision to browserify.

After that comparision you'll also find a comparision to require.js.

On the next slide you'll find a comparision to require.js.

Comparison to browserify

browserifywebpack
only CommonJS sync require also AMD-style require
single bundle Code Splitting
many kinds of expressions in require
plugins loaders
replacements for node.js buildin libaries replacements for node.js buildin libaries
include some path info don't include path info
replacements by alias option by alias option, extension or folder
no minimize minimize with uglifyjs
~ 220b overhead (+ 25b per module) ~ 240b overhead (+ 19b per module)

→ Details

no guarantee for correctness

Comparison to require.js

require.jswebpack
only AMD-style require and wrapped sync require also CommonJS sync require
request per file* or single bundle** Code Splitting
any expression in async require* many kinds of expressions in require
no replacements for node.js buildin libaries replacements for node.js buildin libaries
include some path info don't include path info
replacements by config option by alias option, extension or folder
requires modules from any web path* only modules from filesystem
minimize with uglifyjs or closure compiler** minimize with uglifyjs
~ 14.5kb overhead (+ 19b per module) ~ 240b overhead (+ 19b per module)

→ Details

* unoptimized

** optimized

no guarantee for correctness

Usage (CLI)

Using me is very easy. I assume that you have node.js installed.

You can install the cli:

npm install webpack -g

Typing webpack will show the possible options for the cli.

Compiling is so easy:

webpack ./entryFile.js outputBundle.js

A bit cooler is it with some options

webpack ./entryFile.js outputBundle.js --watch --colors --progress --cache

That recompiles on change, prints status information and with colorful output. It automatically caches unchanged files.

Usage (config)

You may create a webpack.config.js file to specify your options. If you do so you can compile with:

webpack

The webpack.config.js file may look like this:

var path = require("path");
module.exports = {
	context: __dirname,   entry: "./entryFile.js",
	output: {
		path: path.join(__dirname, "assets"),
		publicPath: "http://cdn.example.com/assets/",
		filename: "[hash].bundle.js" },
	cache: true
};

Usage (node.js)

You can also use me from your node.js code.

var webpack = require("webpack");
webpack({
	context: __dirname,
	entry: "./entryFile.js",
	output: {
		path: path.join(__dirname, "assets"),
		publicPath: "http://cdn.example.com/assets/",
		filename: "[hash].bundle.js"
	},
	cache: true
}, function(err, stats) {
	// ...
});

Dev Tools

Because I know that dev tools are very important, there are some dev tools availible:

More details on the next slides.

Dev Tool: express middleware

The dev-middleware can be used in development combined with my watch mode.

It seem to be very simple, but is very useful!

You'll find webpack-dev-middleware here.

Dev Tool: webpack express server

The dev-server uses the dev-middleware to serve a compiled webpack bundle. It also forwards some events emitted by me to the browser, which updates the page or display errors.

Very cool. Just leave a browser window open. It will reflect changes made automatically.

You'll find webpack-dev-server here.

Rich list of more features

WIP

These webpack features are WIP on this presentation

DETAILS