Bhavir Shah (@shahbhavir)
Software developer at Housing.com
module.exports = { greet: function (name) { return 'Hello ' + name; } };
greeter.js
var greeter = require('./greeter'); console.log(greeter.greet('John'));
entry.js
Via command line:
$ webpack entry.js output/bundle.js
... or via config:
module.exports = { entry: './entry', output: { path: 'output', filename: 'bundle.js' } };
webpack.config.js
Keeping things in a configuration file is the way to go for non-trivial setups. If your config file is called webpack.config.js you don't even have to specify the --config parameter to webpack.$ webpack && node output/bundle.js Hash: e7789bda0fc57a510df7 Version: webpack 1.4.15 Time: 28ms Asset Size Chunks Chunk Names bundle.js 1732 0 [emitted] main [0] ./entry.js 72 {0} [built] [1] ./greeter.js 81 {0} [built] Hello JohnThe runtime overhead compared to Browserify and RequireJS: - Webpack: 243b + 20b per module + 4b per dependency - Browserify: 14.7kb + 0b per module + (3b + X) per dependency - RequireJS: 415b + 25b per module + (6b + 2X) per dependency
var greeter = require('./greeter'); console.log(greeter.greet('John'));
entry1.js
var greeter = require('./greeter'); console.log(greeter.greet('Jane'));
entry2.js
module.exports = { entry: { entry1: './entry1', entry2: './entry2' }, output: { path: 'output', filename: 'bundle-[name].js' } };
$ webpack Hash: a4659d84e3692cf36938 Version: webpack 1.4.15 Time: 31ms Asset Size Chunks Chunk Names bundle-entry2.js 1732 0 [emitted] entry2 bundle-entry1.js 1732 1 [emitted] entry1 [0] ./entry1.js 72 {1} [built] [0] ./entry2.js 72 {0} [built] [1] ./greeter.js 81 {0} {1} [built]Webpack outputs a bundle-entry1.js containing entry1.js plus greeter.js, and a bundle-entry2.js containing entry2.js plus greeter.js. The number between curly braces (e.g. {1}) tells you which chunks contain that module. This is not a good solution for a web application, as a user will probably hit multiple entry points in a session, and would have to download common dependencies multiple times.
module.exports = { entry: { entry1: './entry1', entry2: './entry2' }, output: { path: 'output', filename: 'bundle-[name].js' }, plugins: [ new CommonsChunkPlugin('common', 'bundle-[name].js') ] };
webpack.config.js
$ webpack Hash: 75ef3309e9d1f1110c46 Version: webpack 1.4.15 Time: 30ms Asset Size Chunks Chunk Names bundle-entry2.js 172 0 [emitted] entry2 bundle-entry1.js 172 1 [emitted] entry1 bundle-common.js 3842 2 [emitted] common [0] ./entry1.js 72 {1} [built] [0] ./entry2.js 72 {0} [built] [1] ./greeter.js 81 {2} [built]The CommonsChunkPlugin plugin identifies dependencies that are shared among the entry points, and puts them into their own chunk. You end up with bundle-entry1.js containing entry1.js, bundle-entry2.js containing entry2.js, and bundle-common.js containing greeter.js. In this simple example it may seem overkill, but when you are depending on huge libraries, like jQuery, Moment or Angular, it is totally worth it.
var greeter = require('greeter'); console.log(greeter.greet('John'));
entry.js
module.exports = { entry: './entry', output: { path: 'output', filename: 'bundle.js' }, resolve: { modulesDirectories: [ 'utils', 'web_modules', 'node_modules' ] } };
webpack.config.js
Webpack will try to find your dependency in those directories.$ webpack && node output/bundle.js Hash: 23fc5041a118a3dbc1ee Version: webpack 1.4.15 Time: 34ms Asset Size Chunks Chunk Names bundle.js 1732 0 [emitted] main [0] ./entry.js 70 {0} [built] [1] ./utils/greeter.js 81 {0} [built] Hello John
module.exports = greet: (name) -> return "Hello #{name}"
greeter.coffee
Inlined:
var greeter = require('coffee!./greeter'); console.log(greeter.greet('John'));
entry.js
... or via config:
var greeter = require('./greeter'); console.log(greeter.greet('John'));
entry.js
module.exports = { entry: './entry', output: { path: 'output', filename: 'bundle.js' }, module: { loaders: [ { test: /\.coffee$/, loader: 'coffee' } ] }, resolve: { extensions: ['', '.coffee', '.js'] } };
webpack.config.js
We are telling Webpack that all files ending with .coffee should go through the coffee loader. We are also telling it to try the .coffee extension when resolving modules. Much better than inlining, as all your configuration is in one place, so it's much easier to change things.$ webpack && node output/bundle.js Hash: b99cec921bbe2d10542d Version: webpack 1.4.15 Time: 70ms Asset Size Chunks Chunk Names bundle.js 1731 0 [emitted] main [0] ./entry.js 72 {0} [built] + 1 hidden modules Hello John
body { background: transparent url('./bg.png') repeat; }
styles.css
module.exports = { module: { loaders: [ { test: /\.(gif|jpe?g|png)$/, loader: 'url?limit=10000' }, { test: /\.css$/, loader: 'style!css' }, { test: /\.less$/, loader: 'style!css!less?strictMath' } ] } };
webpack.config.js
You can use the file and url loaders to process assets like images. The url loader is just like file, but allows you to inline dependencies under certain conditions. The html and css loaders are able to identify dependencies in HTML files (e.g. <img src="foo.gif" />) and CSS files (e.g. background-image: url('bar.png')) respectively. CSS files need to go through yet another loader, style, to be injected into the head of the HTML document.module.exports = { module: { loaders: [ { test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css') } ] }, plugins: [ new ExtractTextPlugin('bundle.css') ] };
webpack.config.js
If you want to extract CSS content into its own file, you can use the ExtractTextPlugin plugin.$ webpack Hash: f379bf0455c6069d7446 Version: webpack 1.4.15 Time: 200ms Asset Size Chunks Chunk Names bundle.js 2144 0 [emitted] main main.css 512 0 [emitted] main [0] ./entry.js 224 {0} [built] [1] ./greeter.js 81 {0} [built] + 7 hidden modules Child extract-text-webpack-plugin: + 3 hidden modules Child extract-text-webpack-plugin: + 2 hidden modules
module.exports = { module: { preLoaders: [{ test: /\.js$/, exclude: /(node_modules)\//, loader: 'jshint!jscs' }], postLoaders: [{ test: /\.js$/, exclude: /(test|node_modules)\//, loader: 'istanbul-instrumenter' }] } };
webpack.config.js
You can also specify pre- and post-loaders. Here we'd be running our JavaScript files through two linting libraries, and through a code instrumenting library. The order in which loaders are applied is the following: - The file is read from the filesystem - module.preLoaders are applied - module.loaders are applied - Inlined loaders are applied - module.postLoaders are appliedvar t = require('./translator'); module.exports = { greet: function (name) { return t(__('greeting'), {name: name}); } };
greeter.js
Plugins allow us to hook into different phases of the bundling process. For example, the I18nPlugin plugin replaces occurrences of the \_\_ function with strings from a dictionary (e.g. __("Hello World") is replaced with "Hello World" or "Hola Mundo", depending on the current locale).var greeter = require('./greeter'); if (DEBUG) { console.log('Greeting in "%s"', LANGUAGE); } console.log(greeter.greet('John'));
entry.js
The DefinePlugin plugin allows us to define free variables, like DEBUG and LANGUAGE. The value of those variables is specified in the config file.var langs = { en: require('./languages/en.json'), es: require('./languages/es.json') };
webpack.config.js
module.exports = Object.keys(langs).map(function (l) { return { entry: './entry', output: { path: 'output', filename: 'bundle-' + l + '.js' }, plugins: [ new DefinePlugin({ DEBUG: !!process.env.DEBUG, LANGUAGE: '"' + l + '"' }), new I18nPlugin(langs[l]) ] }; });
webpack.config.js (continued)
We are generating a bundle for each of the languages in the langs object, storing the language code in the LANGUAGE variable. We are also defining the value of DEBUG through an environment variable.$ DEBUG=true webpack && node output/bundle-es.js Hash: ee58c1b0671117477901e05a75f21919ca322975 Version: webpack 1.4.15 ... Greeting in "es" Hola JohnWhen we bundle the app with the DEBUG environment variable set to true, we see the debugging statement.
// ... function(module, exports, __webpack_require__) { var greeter = __webpack_require__(1); if (true) { console.log('Greeting in "%s"', ("en")); } console.log(greeter.greet('John')); }, // ...
bundle.js
The DEBUG variable got replaced with true.$ webpack -p && node output/bundle-es.js Hash: 59ade9c7a4dd53e1e2cc2879e3ba1f8c6b79eea5 Version: webpack 1.4.15 WARNING in (undefined) bundle-en.js from UglifyJs Condition always false [./entry.js:2,0] Dropping unreachable code [./entry.js:3,0] WARNING in (undefined) bundle-es.js from UglifyJs Condition always false [./entry.js:2,0] Dropping unreachable code [./entry.js:3,0] ... Hola JohnIf we don't specify the DEBUG environment variable, the condition in the if statement is always false. That's why the whole block gets dropped by UglifyJS when we enable optimizations with the -p flag, and we don't see the debugging statement in the output.
require('./lib/' + name + '.js');A context is created if your request contains expressions, so the exact module is not known at compile time.
var req = require.context('./lib', true, /^\.\/.*\.js$/); var libs = req.keys(); var lib = libs[Math.floor(Math.random() * libs.length)]; console.log(req(lib).foo());
entry.js
You can also create contexts by hand through the require.context function. Here we are using that functionality to require a random module from the lib folder.$ webpack Hash: d0078b76688772738490 Version: webpack 1.4.15 Time: 38ms Asset Size Chunks Chunk Names bundle.js 2631 0 [emitted] main [0] ./entry.js 167 {0} [built] [1] ./lib ^\.\/.*\.js$ 193 {0} [built] [2] ./lib/a.js 63 {0} [optional] [built] [3] ./lib/b.js 63 {0} [optional] [built] [4] ./lib/c.js 63 {0} [optional] [built]Webpack includes all modules matching our regular expression in the bundle.
$ node output/bundle.js c $ node output/bundle.js bAt runtime it does the right thing.
Why would anyone want to do this?
var moment = require('moment'); console.log(moment().format('dddd'));
entry.js
Some third-party libraries, like Moment, also create contexts when processed through Webpack.$ webpack Hash: 3fa34cb738076f531876 Version: webpack 1.4.15 Time: 396ms Asset Size Chunks Chunk Names bundle.js 393777 0 [emitted] main [0] ./entry.js 70 {0} [built] + 81 hidden modulesWhy is the bundle so big?
function loadLocale(name) { var oldLocale = null; if (!locales[name] && hasModule) { try { oldLocale = moment.locale(); require('./locale/' + name); moment.locale(oldLocale); } catch (e) { } } return locales[name]; }
moment.js
Webpack is creating a context and including all locales in the bundle.module.exports = { entry: './entry', output: { path: 'output', filename: 'bundle.js' }, plugins: [ new ContextReplacementPlugin( /moment[\\\/]locale$/, new RegExp('^\\./en$') ) ] };
webpack.config.js
We can use the ContextReplacementPlugin plugin to manipulate the context. Here, we are only including the English locale.$ webpack Hash: d6a652b194a14ca3d0a6 Version: webpack 1.4.15 Time: 141ms Asset Size Chunks Chunk Names bundle.js 101653 0 [emitted] main [0] ./entry.js 70 {0} [built] + 3 hidden modulesThe resulting bundle is much smaller, because we've left all other locales out.
var a = require('./a'); var p = function () { console.log(arguments); }; a.foo(p); a.bar(p);
entry.js
module.exports = { foo: function (callback) { callback('foo'); }, bar: function (callback) { require.ensure(['./b'], function (require) { require('./b').bar(callback); }); } };
a.js
Calling require.ensure here will create a split point that will put b into its own chunk. This chunk will be loaded on demand when the bar method is called.module.exports = { bar: function (callback) { callback('bar'); } };
b.js
$ webpack Hash: 0b184470f56d6ed09471 Version: webpack 1.4.15 Time: 31ms Asset Size Chunks Chunk Names bundle.js 4098 0 [emitted] main 1.bundle.js 180 1 [emitted] [0] ./entry.js 96 {0} [built] [1] ./a.js 203 {0} [built] [2] ./b.js 76 {1} [built]You can see that b has been split into its own chunk.
$ webpack-dev-server $ open http://localhost:8080/bundleYou can see this in action by launching webpack-dev-server.
https://shahbhavir.github.io/webpack-talk/
Credits: (Daniel)