How do I use Webpack to create independent SPA bundles that may or may not be loaded on the fly as my user navigates my SPA?
I have a contacts module, and a tasks module. Both have two dependencies. I want WebPack to create bundles for each that are loaded when (and if) needed.
The code is below. The problem appears to be that each of these entries are being seen as application entry points, and so are getting webpack bootstrap code inserted therein.
I've seen various examples with CommonsChunkPlugin
but I can't find an API reference / documentation for it, and from what I can surmise, that's not what I want anyway.
Edit - found those docs here, and added an attempt with that plugin below in my edit.
Current configuration
module.exports = {
entry: {
contacts: './contacts',
tasks: './tasks'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
}
};
Contacts.js
define(['./ca', './cb'], function(ca, cb){
var name = 'Contacts';
alert(ca + ' ' + cb);
});
Tasks.js
define(['./ta', './tb'], function(ta, tb){
var name = 'TASKS Main';
alert(ta + ' ' + tb);
});
tasks-bundle.js
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(3), __webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function(ta, tb){
var name = 'TASKS Main';
alert(ta + ' ' + tb);
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
/***/ },
/* 1 */,
/* 2 */,
/* 3 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){
var name = 'TASKS - A';
alert('ta');
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
/***/ },
/* 4 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){
var name = 'TASKS - B';
alert('tb');
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
/***/ }
/******/ ]);
EDIT
Here's my attempt number 2 with the CommonsChunkPlugin. I created a dummy app.js
app.js
var module = window.location.hash.split('/')[0];
alert(module);
Then I moved all of my contacts and tasks files under a components folder, but otherwise left them alone. My new configuration:
module.exports = {
entry: {
app: './app'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: './components/contacts',
filename: 'contacts-component-bundle.js'
}),
new webpack.optimize.CommonsChunkPlugin({
name: './components/tasks',
filename: 'tasks-component-bundle.js'
})
]
};
Bizarely, now app-bundle.js appears to not have any Webpack bootstrap code
webpackJsonp([0,1,2],[
/* 0 */
/***/ function(module, exports) {
var module = window.location.hash.split('/')[0];
alert(module);
/***/ }
]);
contacts-components-bundle.js now just has this
webpackJsonp([1,2],[]);
and tasks-components-bundle.js appears to have all of my webpack bootstrap code
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, callbacks = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId])
/******/ callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/ while(callbacks.length)
/******/ callbacks.shift().call(null, __webpack_require__);
/******/ if(moreModules[0]) {
/******/ installedModules[0] = 0;
/******/ return __webpack_require__(0);
/******/ }
/******/ };
/******/ // The module cache
/******/ var installedModules = {};
/******/ // object to store loaded and loading chunks
/******/ // "0" means "already loaded"
/******/ // Array means "loading", array contains callbacks
/******/ var installedChunks = {
/******/ 2:0,
/******/ 1:0
/******/ };
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/ // "0" is the signal for "already loaded"
/******/ if(installedChunks[chunkId] === 0)
/******/ return callback.call(null, __webpack_require__);
/******/ // an array means "currently loading".
/******/ if(installedChunks[chunkId] !== undefined) {
/******/ installedChunks[chunkId].push(callback);
/******/ } else {
/******/ // start chunk loading
/******/ installedChunks[chunkId] = [callback];
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = 'text/javascript';
/******/ script.charset = 'utf-8';
/******/ script.async = true;
/******/ script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"app","1":"./components/contacts"}[chunkId]||chunkId) + "-bundle.js";
/******/ head.appendChild(script);
/******/ }
/******/ };
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ })
/************************************************************************/
/******/ ([]);
Again, I'm just trying to use Webpack to get an SPA proof of concept up and running, with some sort of root app.js entry point, and then some arbitrary number of modules / components which are loaded on demand. This is trivially easy with requirejs, so I have to imagine I'm missing something key here, especially with all the articles I've seen talking about how great Webpack is for SPAs.
EDIT 2
Per bebraw's answer below I tried the following:
app.js
var mod = window.location.hash.split('/')[0];
alert(mod);
require.ensure([], function() {
require('./components/' + mod).show();
});
webpack.config.js
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: {
app: './app'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
}
};
And then in my build folder I'm left with app-bundle.js which has all my bootstrap code, and my app.js code, and then 1.1-bundle.js which has all of my tasks and contacts code.
I've also tried this
module.exports = {
entry: {
app: './app'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name]-bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: './components/contacts',
filename: 'contacts-component-bundle.js',
children: true
}),
new webpack.optimize.CommonsChunkPlugin({
name: './components/tasks',
filename: 'tasks-component-bundle.js',
children: true
})
]
};
Which yields the same as the above, but now also has tasks-component-bundle.js and contacts-component-bundle.js, both of which have only some webpack bootstrap code; the tasks and contacts code are all still in 1.1-bundle.
Again, I simply want to be able to tell Webpack, one way or another, to bundle up individual modules and their dependencies for subsequent lazy, async loading when needed.
The final answer was given by Tobias—Webpack creator—below, which I'll put here for posterity.
Truly dynamic is not possible. webpack (in constract to require.js) compiles your app before executing it, and don't have access to runtime information. Dynamic requires in webpack dive in every possible folder as long your dynamic expression don't contain ... You should even be able to configure it to use mod + '/' + mod with the ContextReplacementPlugin and a little RegExp magic (use backreferences in the RegExp). By default it would include too many modules.