
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
- your arrow keys (←, →),
- the space bar or
- the buttons on the left and the right of the screen.
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)
- Are you a
javascriptdeveloper? If not, leave this website, the content is not relevant to you. - Do you know
node.js? I hope so. Elsewise read nodejs.org. - Do you know
CommonJSandAMD? . Elsewise I've some slides for you. - Do you know
browserify? . I'll show you the differences. - Do you know
require.js? . I'll compare it with me to.
Content
Todays Web Apps: Motivation
Today websites are evolving into web apps:
- More and more javascript is in a page.
- One can do more stuff in modern browsers.
- Less full page reloads → even more code in a page.
→ 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- CommonJS
- AMD and some dialects of it
- ...
<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:
- Conflicts in the global object
- Order is important
- Developer have to resolve dependencies of thrid-party modules/libaries
- In big projects the list can get really long, difficult to manage
CommonJS: synchron require
This is the style you use in node.js on server side.
require("module"); require("../file.js");
Pro
- Reuse server-side modules, npm
- There are already many modules in this style
- very simple
Contra
- blocking calls do not apply well on networks. Requests are asynchronous.
- No parallel require of multiple modules
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
- Fits the asynchronous request style
- Parallel loading of multiple modules
Contra
- Coding overhead
- Seem to be some kind of workaround
Existing module systems
CommonJS
- node.js - only server-side
- browserify - compile to one bundle
- modules-webmake - compile to one bundle
- wreq - fully client-side
As you know browserify, I'll compare it to me on a later slide.
AMD
- require.js - fully client-side
- curl - fully client-side
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:
- 1 request/file per module (i. e.
require.jsin dev mode) - All modules in a request/file (i. e.
browserify,require.jsbundled)
Both are used in the wild (preferable the second one), but both are suboptimal:
- If there is a request/file per module,
- only required modules are transferred,
- but there are many requests which means much overhead.
- If there is 1 request/file with all modules,
- it would be less overhead,
- but not (yet) required modules are transferred too.
Transferring. A flexible solution
A more flexible transferring is better.
→ Chunked concatination
- Split the code base into multiple smaller requests/files/chunks.
- Transfer chunks on demand.
- The split points are up to you as developer.
- This make me really flexible and useable for big code bases.
- It's optional to use. You may don't need it for small projects.
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.idis 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"
- Use AMD-style
require, or - use the
require.ensurefunction.
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)
modulesis a list of modules that are included in the chunk additically to the dependencies in thecallbackfunction.callbackfunction with code to run after the chunk was loaded. Calls torequirein the function's body are parsed and placed into the chunk.
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
- code of that file
- code of module a and dependencies
- code of module b and dependencies
File/Chunk 2: 1.bundle.js (loaded when calling require.ensure)
- code of module c and dependencies (but code is not used)
- code of module d and dependencies
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:
- stylesheets
- images
- webfonts
- html for templating
- etc.
And also:
- coffeescript → javascript
- less stylesheets → css stylesheets
- jade templates → javascript which generates html
- i18n files → something
- big etc.
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...
- ... is a javascript function preprocessing a file.
- ... is running in
node.js, so possibilities are endless. - ... can be piped together with other loaders, but the last loader should emit javascript.
- ... can opt-in for optional execution in a seperate process (cpu-heavy stuff).
- ... can be passed options.
- ... can be published/installed though
npm. - ... can be asynchronous.
- ... can be bound to extensions/RegExps.
- ... can emit files.
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);
});
}
Some loader examples
Here is a small part of this loaders list:
- Basic:
json,raw,worker(WebWorker) - Styling:
css,less,style - Templates:
jade - Languages:
coffee - Support:
mocha,jshint
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.
- It is possible to use loaders with contexts.
- In the most cases contexts are transparent for the developer und just work.
- Limitation: It is assumed that non-compile-time-constant parts do not contain any
"/../". - Common Problem: Maybe to many files are included. This may not be your intent.
- The
?:-operation do not create a context. - Copying the require function into another variable creates a context for the current directory.
- There is a warning in this case.
- Files with errors are not included. There is a warning in this case.
- You can create a context also with
require.context(path, recursive, regExp).- It return a function to require from that folder.
- The function has a method
.keys()returning all possible parameters.
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:
- Code Splitting
- Support of CommonJS and AMD
- Configuration
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
| browserify | webpack |
|---|---|
| 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) |
no guarantee for correctness
Comparison to require.js
| require.js | webpack |
|---|---|
| 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) |
* 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.
--debugordebug:true: Switch loaders into debug mode. (i. e. the jade loader includes line numbers and the source code for good error messages)--devtool evalordevtool:"eval": Emits//@ SourceURLcomments for debugging support. Try Chrome Dev Tools (or Firebug).webpack-dev-middleware: middleware for express.webpack-dev-server: express server with auto-refresh.
Dev Tool: express middleware
The dev-middleware can be used in development combined with my watch mode.
- Compilation in memory. No file write access.
- Requests block until compilation is done. You'll ever use the newest bundle.
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
- I can add a hash of the bundle content to the filename for better http caching.
- Try
enhanced-requireto use loaders, contexts, etc. in node.js. - There is a grunt task (
grunt-webpack) to include me in your build process. - I have many config options. You can customize me to fit to your imagination.
- I can cache previous module results to support faster re-compilation.
- I have some browser replacement for node.js buildin libaries included.
- You have various options to apply browser replacements for anything.
- I have a rich plugin system to enhance me with your custom stuff.
- I can compile multiple entry points with shared chunks for multi page apps.
WIP
These webpack features are WIP on this presentation
- jade usage
- less usage
- coffeescript usage
- Pull Requests are welcome: Additional info and/or fixes