├── .gitmodules ├── app ├── viewModels │ ├── about │ │ ├── about.js │ │ └── about.html │ ├── widgets │ │ ├── widgets.js │ │ └── widgets.html │ ├── router │ │ ├── viewModels │ │ │ ├── nested │ │ │ │ ├── nested.js │ │ │ │ └── nested.html │ │ │ └── how │ │ │ │ ├── how.js │ │ │ │ └── how.html │ │ ├── index.js │ │ ├── routes.js │ │ └── index.html │ ├── class.js │ └── dialogs │ │ ├── viewModels │ │ ├── hello │ │ │ ├── hello.js │ │ │ └── hello.html │ │ └── prompt │ │ │ ├── prompt.html │ │ │ └── prompt.js │ │ ├── dialogs.html │ │ └── dialogs.js ├── widgets │ ├── index.js │ └── alert │ │ ├── alert.html │ │ └── alert.js ├── shell.js ├── overrides │ ├── composition.js │ ├── views.js │ ├── widget.js │ └── system.js ├── main.js ├── shell.html └── routes.js ├── README.md ├── .gitignore ├── dist ├── 3.chunk.js ├── 2.chunk.js └── 1.chunk.js ├── index.html ├── package.json ├── LICENSE ├── webpack.config.js └── .eslintrc /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/viewModels/about/about.js: -------------------------------------------------------------------------------- 1 | var ViewModel = require('viewModels/class'); 2 | 3 | var Home = new ViewModel({ 4 | view: require('./about.html') 5 | }); 6 | 7 | module.exports = Home; 8 | -------------------------------------------------------------------------------- /app/viewModels/widgets/widgets.js: -------------------------------------------------------------------------------- 1 | var ViewModel = require('viewModels/class'); 2 | 3 | var Widgets = new ViewModel({ 4 | view: require('./widgets.html') 5 | }); 6 | 7 | module.exports = Widgets; 8 | -------------------------------------------------------------------------------- /app/viewModels/router/viewModels/nested/nested.js: -------------------------------------------------------------------------------- 1 | var ViewModel = require('viewModels/class'); 2 | 3 | var Nested = new ViewModel({ 4 | view: require('./nested.html') 5 | }); 6 | 7 | module.exports = Nested; -------------------------------------------------------------------------------- /app/widgets/index.js: -------------------------------------------------------------------------------- 1 | // Note the capitalisation here for widget names. We do this so can 2 | // easily distinguish between normal binding handlers and widgets. 3 | module.exports = { 4 | Alert: require('./alert/alert') 5 | }; 6 | -------------------------------------------------------------------------------- /app/viewModels/router/viewModels/how/how.js: -------------------------------------------------------------------------------- 1 | var router = require('plugins/router'); 2 | var ViewModel = require('viewModels/class'); 3 | 4 | var How = new ViewModel({ 5 | view: require('./how.html') 6 | }); 7 | 8 | module.exports = How; 9 | -------------------------------------------------------------------------------- /app/widgets/alert/alert.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/shell.js: -------------------------------------------------------------------------------- 1 | var router = require('plugins/router'); 2 | var ViewModel = require('viewModels/class'); 3 | 4 | var Shell = new ViewModel({ 5 | view: require('./shell.html') 6 | }); 7 | 8 | Shell.router = router.map( 9 | require('./routes') 10 | ) 11 | .buildNavigationModel(); 12 | 13 | Shell.activate = function() { 14 | return router.activate(); 15 | }; 16 | 17 | module.exports = Shell; 18 | -------------------------------------------------------------------------------- /app/viewModels/router/index.js: -------------------------------------------------------------------------------- 1 | var router = require('plugins/router'); 2 | var ViewModel = require('viewModels/class'); 3 | 4 | var Index = new ViewModel({ 5 | view: require('./index.html') 6 | }); 7 | 8 | Index.router = router.createChildRouter() 9 | .makeRelative({ fromParent: true }) 10 | .map( 11 | require('./routes') 12 | ) 13 | .buildNavigationModel(); 14 | 15 | module.exports = Index; 16 | -------------------------------------------------------------------------------- /app/viewModels/router/viewModels/nested/nested.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | You can also create nested, asynchronous routes, by using Durandals 4 | router.createChildRouter functionality. 5 |

6 | 7 |

8 | This tab is bundled in a separate file from both the parent page and main app! 9 | Check the network tab to see this in action (look for the 3.chunk.js file)! 10 |

11 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Durandal + Webpack 2 | 3 | This is a sample project that demonstrates the use of Webpack with Durandal together to bundle your SPA. 4 | 5 | ## Quickstart 6 | 7 | Install the package dependencies with 8 | ``` 9 | npm install 10 | ``` 11 | 12 | Start the webpack at `localhost:8080` with 13 | ``` 14 | npm run dev 15 | ``` 16 | 17 | Go forth! 18 | 19 | #### [Read the full blog series here](http://blog.craigsworks.com/durandal-and-webpack-introduction) 20 | -------------------------------------------------------------------------------- /app/viewModels/class.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | function ViewModel(options) { 4 | options || (options = {}); 5 | 6 | this.options = options; 7 | this.view = options.view; 8 | } 9 | 10 | // Override the default `getView` logic that Durandal utilises to 11 | // fetch the view for each ViewModel instance. 12 | ViewModel.prototype.getView = function() { 13 | var view = $.trim(this.view); 14 | return $.parseHTML(view)[0]; 15 | }; 16 | 17 | module.exports = ViewModel; 18 | -------------------------------------------------------------------------------- /app/overrides/composition.js: -------------------------------------------------------------------------------- 1 | var system = require('durandal/system'); 2 | var composition = require('durandal/composition'); 3 | 4 | var compose = composition.compose; 5 | composition.compose = function(element, settings) { 6 | // If the `model` isn't a `moduleId` string, assume it's the module 7 | // itself and resolve it using the `system` module 8 | if('string' !== typeof settings.model) { 9 | settings.model = system.resolveObject(settings.model); 10 | } 11 | 12 | // super() 13 | return compose.apply(this, arguments); 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /app/viewModels/dialogs/viewModels/hello/hello.js: -------------------------------------------------------------------------------- 1 | var ko = require('knockout'); 2 | var dialog = require('plugins/dialog'); 3 | var ViewModel = require('viewModels/class'); 4 | 5 | function Hello() { 6 | this.message = ko.observable(''); 7 | this.text = ko.observable(''); 8 | this.title = ko.observable(''); 9 | this.canClose = ko.observable(true); 10 | }; 11 | 12 | Hello.prototype.view = require('./hello.html'); 13 | 14 | Hello.prototype.getView = ViewModel.prototype.getView; 15 | 16 | Hello.prototype.close = function() { 17 | return dialog.close(this, null); 18 | }; 19 | 20 | module.exports = Hello; -------------------------------------------------------------------------------- /app/viewModels/router/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | route: '', 4 | title: 'What?', 5 | moduleId: function() { 6 | return require('./viewModels/how/how'); 7 | }, 8 | nav: true 9 | }, 10 | 11 | // We can nest async routes, too, allowing us to conviniently 12 | // separate out various parts of our application into distinct, 13 | // async loaded parts. Beautiful! 14 | { 15 | route: 'nested', 16 | title: 'One other thing...', 17 | moduleId: function(cb) { 18 | require(['./viewModels/nested/nested'], function(module) { 19 | cb(null, module); 20 | }); 21 | }, 22 | nav: true 23 | } 24 | ]; 25 | -------------------------------------------------------------------------------- /app/overrides/views.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var system = require('durandal/system'); 3 | var viewLocator = require('durandal/viewLocator'); 4 | 5 | // Allow using `function` or bare HTML string as a view 6 | var locateView = viewLocator.locateView; 7 | viewLocator.locateView = function(viewOrUrlOrId, area) { 8 | var viewId; 9 | 10 | // HTML here will be passed into `processMarkup` 11 | if('string' === typeof viewOrUrlOrId && $.trim(viewOrUrlOrId).charAt(0) === '<') { 12 | return system.defer(function(dfd) { 13 | dfd.resolve(viewOrUrlOrId); 14 | }); 15 | } 16 | 17 | // super() 18 | return locateView.apply(this, arguments); 19 | }; 20 | -------------------------------------------------------------------------------- /app/viewModels/router/viewModels/how/how.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Along with the ability to bundle all the routes together, we can also optionally bundle specific 4 | parts of the application together into chucks we can grab on-the-fly when the user goes to 5 | specific routes! 6 |

7 |

8 | In fact... this very page was requested separately from the main app.js. 9 | Check the network tab to see this in action (look for the 1.chunk.js file)! 10 | 11 |

12 | This is great for large apps where we don't need or want the user to download the entirety of 13 | the application code at once. 14 |

15 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Non-NPM dependencies 30 | web_components -------------------------------------------------------------------------------- /app/viewModels/dialogs/viewModels/hello/hello.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/3.chunk.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([3],{ 2 | 3 | /***/ 37: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | var ViewModel = __webpack_require__(4); 7 | 8 | var Nested = new ViewModel({ 9 | view: __webpack_require__(47) 10 | }); 11 | 12 | module.exports = Nested; 13 | 14 | /***/ }, 15 | 16 | /***/ 47: 17 | /***/ function(module, exports) { 18 | 19 | module.exports = "
\n\t

\n\t\tYou can also create nested, asynchronous routes, by using Durandals \n\t\trouter.createChildRouter functionality. \n\t

\n\n\t

\n\t\tThis tab is bundled in a separate file from both the parent page and main app!\n\t\tCheck the network tab to see this in action (look for the 3.chunk.js file)!\n\t

\n
"; 20 | 21 | /***/ } 22 | 23 | }); -------------------------------------------------------------------------------- /app/viewModels/widgets/widgets.html: -------------------------------------------------------------------------------- 1 |
2 |

Widgets

3 |

4 | Utilising Durandals widget functionality is simply a case of re-using the same 5 | getView logic discussed in the Composition section! 6 |

7 |
8 | 9 |
10 |

Alerts

11 | 12 | 18 | 19 | 25 |
26 | -------------------------------------------------------------------------------- /app/viewModels/dialogs/viewModels/prompt/prompt.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Durandal + Webpack 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/widgets/alert/alert.js: -------------------------------------------------------------------------------- 1 | var ko = require('knockout'); 2 | var ViewModel = require('viewModels/class'); 3 | 4 | var Alert = function() { 5 | this.variant = ko.observable(''); 6 | this.text = ko.observable(''); 7 | this.title = ko.observable(''); 8 | this.canClose = ko.observable(true); 9 | 10 | this.className = ko.pureComputed(function() { 11 | var classes = [ 12 | 'alert-' + this.variant() 13 | ]; 14 | 15 | if(this.canClose()) { 16 | classes.push('alert-dismissable'); 17 | } 18 | 19 | return classes.join(' '); 20 | }, 21 | this); 22 | }; 23 | 24 | Alert.prototype.view = require('./alert.html'); 25 | 26 | Alert.prototype.getView = ViewModel.prototype.getView; 27 | 28 | Alert.prototype.activate = function(settings) { 29 | this.variant(settings.variant || 'danger'); 30 | this.text(settings.text); 31 | this.title(settings.title); 32 | this.canClose(settings.canClose !== false); 33 | }; 34 | 35 | module.exports = Alert; -------------------------------------------------------------------------------- /app/viewModels/dialogs/viewModels/prompt/prompt.js: -------------------------------------------------------------------------------- 1 | var ko = require('knockout'); 2 | var dialog = require('plugins/dialog'); 3 | var ViewModel = require('viewModels/class'); 4 | 5 | function Prompt() { 6 | this.message = ko.observable(''); 7 | this.text = ko.observable(''); 8 | this.title = ko.observable(''); 9 | this.canClose = ko.observable(true); 10 | }; 11 | 12 | Prompt.prototype.view = require('./prompt.html'); 13 | 14 | Prompt.prototype.getView = ViewModel.prototype.getView; 15 | 16 | Prompt.prototype.activate = function(message, initialText, canClose) { 17 | this.message(message); 18 | this.text(initialText); 19 | this.canClose(canClose !== false); 20 | }; 21 | 22 | Prompt.prototype.selectOption = function(result) { 23 | if(result === true) { 24 | result = this.text(); 25 | } 26 | 27 | return dialog.close(this, result); 28 | }; 29 | 30 | Prompt.prototype.close = function() { 31 | return dialog.close(this, null); 32 | }; 33 | 34 | module.exports = Prompt; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "durandal-webpack", 3 | "version": "1.0.0", 4 | "description": "Durandal + Webpack = Awesome", 5 | "main": "index.js", 6 | "dependencies": { 7 | "durandal": "^2.1.0", 8 | "jquery": "^2.1.4", 9 | "knockout": "^3.3.0" 10 | }, 11 | "devDependencies": { 12 | "html-loader": "^0.3.0", 13 | "webpack-dev-server": "^1.15.0", 14 | "webpack": "^1.10.5" 15 | }, 16 | "scripts": { 17 | "dev": "webpack-dev-server", 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/Craga89/durandal-webpack.git" 23 | }, 24 | "keywords": [ 25 | "durandal", 26 | "webpack" 27 | ], 28 | "author": "Craig Michael Thompson", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/Craga89/durandal-webpack/issues" 32 | }, 33 | "homepage": "https://github.com/Craga89/durandal-webpack#readme" 34 | } 35 | -------------------------------------------------------------------------------- /app/overrides/widget.js: -------------------------------------------------------------------------------- 1 | var widget = require('plugins/widget'); 2 | 3 | // Import the widgets index module, which should export an object 4 | // whose keys are the names of the Widgets to register and the 5 | // values the Widget modules 6 | var widgets = require('widgets/index'); 7 | 8 | // Widgets usually require a `moduleId` to resolve. We'll override this so 9 | // we return the ViewModel class directly, by looking it up in the `widgets/index` 10 | // file by key. 11 | widget.convertKindToModulePath = function(name) { 12 | var widget = widgets[ name ]; 13 | if(!widget) { 14 | console.error('Missing or invalid widget requested: ' + name); 15 | } 16 | 17 | return widget; 18 | }; 19 | 20 | // By default, Durandal will attempt to retrieve the view for a widget using 21 | // the `mapKindToViewId` and pass it along to the `composition` engine. We'll 22 | // do away with this, and force it to use the notmal `getView` method instead. 23 | widget.mapKindToViewId = function() { }; -------------------------------------------------------------------------------- /app/viewModels/dialogs/dialogs.html: -------------------------------------------------------------------------------- 1 |
2 |

Dialogs

3 |

4 | Dialogs can be easily integrate into the Webpack build by referencing the ViewModel 5 | as a direct dependency, and passing it to dialog.show as the first 6 | parameter, instead of the usual moduleId. 7 |

8 |
9 | 10 | 18 |
19 | 20 |
21 |

Asynchronous

22 |

23 | We can also fetch dialogs on request using Webpacks require.ensure Code Splitting 24 | functionality! 25 |

26 |

27 | Alert 28 |

29 |
30 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | var ko = require('knockout'); 2 | var app = require('durandal/app'); 3 | var system = require('durandal/system'); 4 | var widget = require('plugins/widget'); 5 | 6 | // Durandal core overrides - Required for Webpack 7 | require('overrides/system'); 8 | require('overrides/composition'); 9 | require('overrides/views'); 10 | require('overrides/widget'); 11 | 12 | // Webpack sets this __DEV__ variable. See `webpack.config.js` file 13 | if(__DEV__) { 14 | system.debug(true); 15 | 16 | window.ko = ko; 17 | window.app = app; 18 | window.router = router; 19 | } 20 | 21 | // Install the router 22 | var router = require('plugins/router'); 23 | router.install({}); 24 | 25 | 26 | // Install widgets 27 | var widgets = require('widgets/index'); 28 | widget.install({ 29 | kinds: Object.keys(widgets) 30 | }); 31 | 32 | // Start the appliction 33 | app.start().then(function () { 34 | // Set the title 35 | app.title = 'Durandal + Webpack'; 36 | 37 | // Show the app by setting the root view model for our application with a transition. 38 | var shell = require('./shell'); 39 | return app.setRoot(shell); 40 | }); 41 | -------------------------------------------------------------------------------- /app/overrides/system.js: -------------------------------------------------------------------------------- 1 | var system = require('durandal/system'); 2 | 3 | var acquire = system.acquire; 4 | system.acquire = function(moduleIdOrModule) { 5 | var isModule = typeof moduleIdOrModule !== 'string' && !(moduleIdOrModule instanceof Array); 6 | if(isModule) { 7 | return system.defer(function(dfd) { 8 | // If the moduleId is a function... 9 | if(moduleIdOrModule instanceof Function) { 10 | // Execute the function, passing a callback that should be 11 | // called when the (possibly) async operation is finished 12 | var result = moduleIdOrModule(function(err, module) { 13 | if(err) { dfd.reject(err); } 14 | dfd.resolve(module); 15 | }); 16 | 17 | // Also allow shorthand `return` from the funcction, which 18 | // resolves the Promise with whatever was immediately returned 19 | if(result !== undefined) { 20 | dfd.resolve(result); 21 | } 22 | } 23 | 24 | // If the moduleId is actually an object, simply resolve with it 25 | else { 26 | dfd.resolve(moduleIdOrModule); 27 | } 28 | }); 29 | } 30 | 31 | // super() 32 | return acquire.apply(this, arguments); 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Craig Michael Thompson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/viewModels/router/index.html: -------------------------------------------------------------------------------- 1 |
2 |

Router

3 |

4 | We can interface nicely with the router component, too. You're seeing that in action now! 5 |

6 |
7 | 8 |
9 |

How?

10 |

11 | Typically Durandals router module requires you to specify a moduleId 12 | for each route. 13 |

14 | 15 |

16 | To allow Webpack to know these are actual dependencies, we instead set each 17 | moduleId to the actual ViewModel module instance instead, and override the core 18 | Durandal composition and system modules to handle it. 19 |

20 |
21 |
22 | 23 |
24 |

Asynchronous Routes!

25 | 26 | 31 | 32 |
33 |
34 |
-------------------------------------------------------------------------------- /app/viewModels/dialogs/dialogs.js: -------------------------------------------------------------------------------- 1 | var system = require('durandal/system'); 2 | var dialog = require('plugins/dialog'); 3 | var ViewModel = require('viewModels/class'); 4 | 5 | var Dialogs = new ViewModel({ 6 | view: require('./dialogs.html') 7 | }); 8 | 9 | Dialogs.alert = function() { 10 | return dialog.showMessage('Sample alert message', 'Alert!'); 11 | }; 12 | 13 | Dialogs.confirm = function() { 14 | return dialog.showMessage('Sample confirmation dialog', 'Confirm', ['OK', 'Cancel']); 15 | }; 16 | 17 | Dialogs.prompt = function() { 18 | var Dialog = require('./viewModels/prompt/prompt'); 19 | 20 | return dialog.show( new Dialog(), [ 21 | 'Enter some text' 22 | ]) 23 | 24 | .then(function(result) { 25 | console.info('User entered: ', result); 26 | }); 27 | }; 28 | 29 | Dialogs.hello = function() { 30 | return system.defer(function(dfd) { 31 | require(['./viewModels/hello/hello'], dfd.resolve); 32 | }) 33 | 34 | .then(function(Dialog) { 35 | return dialog.show( new Dialog(), [ 36 | 'Enter some text' 37 | ]); 38 | }) 39 | 40 | .then(function(result) { 41 | console.info('User entered: ', result); 42 | }); 43 | }; 44 | 45 | module.exports = Dialogs; 46 | -------------------------------------------------------------------------------- /app/shell.html: -------------------------------------------------------------------------------- 1 |
2 | 37 | 38 |
39 |
-------------------------------------------------------------------------------- /dist/2.chunk.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([2],{ 2 | 3 | /***/ 32: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | var ko = __webpack_require__(2); 7 | var dialog = __webpack_require__(10); 8 | var ViewModel = __webpack_require__(4); 9 | 10 | function Hello() { 11 | this.message = ko.observable(''); 12 | this.text = ko.observable(''); 13 | this.title = ko.observable(''); 14 | this.canClose = ko.observable(true); 15 | }; 16 | 17 | Hello.prototype.view = __webpack_require__(43); 18 | 19 | Hello.prototype.getView = ViewModel.prototype.getView; 20 | 21 | Hello.prototype.close = function() { 22 | return dialog.close(this, null); 23 | }; 24 | 25 | module.exports = Hello; 26 | 27 | /***/ }, 28 | 29 | /***/ 43: 30 | /***/ function(module, exports) { 31 | 32 | module.exports = "
\n\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t×\n\t\t\t\t\n\t\t\t\n\n\t\t\t

Hello world!

\n\t\t
\n\n\t\t
\n\t\t\t

\n\t\t\t\tI was loaded in a separate network request to the rest of the application. I'm not\n\t\t\t\topened that much, so it's probably for the best... :(\n\t\t\t

\n\t\t
\n\n\t\t
\n\t\t\tBye!\n\t\t
\n\t
\n
"; 33 | 34 | /***/ } 35 | 36 | }); -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | var system = require('durandal/system'); 2 | 3 | module.exports = [ 4 | // Here we define our routes as usual, but with one important distinction. 5 | // Our `moduleId` is no longer a string that points to the module, but rather 6 | // the module itself, as an inline, static dependency. This will bundle the 7 | // modules into your main app, but still work as expected in Durandal! 8 | { 9 | route: '', 10 | title: 'About', 11 | moduleId: function() { 12 | return require('viewModels/about/about'); 13 | }, 14 | nav: true 15 | }, 16 | 17 | // An async route, which lets us define certain "Code Splitting" points 18 | // which shouldn't be distributed in the main app.js file, but bundled 19 | // alongside it to be fetched once the user actually goes to this route 20 | // 21 | // Check the Network tab when navigating to this page, you'll see it load 22 | // asynchronously, just like your old Require.js setup. 23 | { 24 | route: 'router*details', 25 | hash: '#router', 26 | title: 'Router', 27 | moduleId: function(cb) { 28 | require(['viewModels/router/index'], function(module) { 29 | cb(null, module); 30 | }); 31 | }, 32 | nav: true 33 | }, 34 | 35 | { 36 | route: 'dialogs', 37 | title: 'Dialogs', 38 | moduleId: function() { 39 | return require('viewModels/dialogs/dialogs'); 40 | }, 41 | nav: true 42 | }, 43 | 44 | { 45 | route: 'widgets', 46 | title: 'Widgets', 47 | moduleId: function() { 48 | return require('viewModels/widgets/widgets'); 49 | }, 50 | nav: true 51 | } 52 | ]; 53 | -------------------------------------------------------------------------------- /app/viewModels/about/about.html: -------------------------------------------------------------------------------- 1 |
2 |

What is this?

3 |

4 | This is a sample project that demonstrates the use of Webpack with Durandal together to bundle your SPA. 5 |

6 |
7 | 8 |
9 |

Why would I use it?

10 |

11 | Durandal is a SPA framework 12 | built atop the popular Require.js specification, allowing users to modularise their code and asynchronously 13 | fetch their dependenices on request. 14 |

15 |

16 | Utilising Webpack in place of Require.js provides many benefits, including allowing us to smarly bundle our application 17 | into distinct chunks, as opposed to completely individual files. This is great for caching, and can give a good boost 18 | to performance, especially on high latency networks. 19 |

20 |

21 | There are plenty of other benefits, including transpilation support, CSS bundling and more! 22 |

23 |
24 |
25 | 26 |
27 |

How do you do it?

28 | 29 |

30 | This project is supported by an in-depth Blog series title "Durandal + Webpack", which should answer any questions 31 | you have about how this was achieved. Make sure to check-out the source for a technical view of what's going on. 32 |

33 | 34 | 35 | 36 | Read the Guide 37 | 38 | 39 | 40 | View the Source 41 | 42 | 43 |
44 | 45 |
-------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var url = require('url'); 3 | var path = require('path'); 4 | var webpack = require('webpack'); 5 | 6 | var DEBUG = !process.argv.production; 7 | 8 | var GLOBALS = { 9 | 'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"', 10 | '__DEV__': DEBUG 11 | }; 12 | 13 | module.exports = { 14 | // Main entry directory and file 15 | entry: { 16 | app: [ 17 | // 'webpack/hot/dev-server', 18 | path.join(__dirname, 'app', 'main.js') 19 | ] 20 | }, 21 | 22 | // Output directories and file 23 | output: { 24 | path: path.join(__dirname, 'dist'), 25 | filename: '[name].js', 26 | chunkFilename: '[name].chunk.js', 27 | publicPath: '/durandal-webpack/dist/' 28 | }, 29 | 30 | // Custom plugins 31 | plugins: [ 32 | new webpack.DefinePlugin(GLOBALS), 33 | new webpack.optimize.OccurenceOrderPlugin() 34 | ] 35 | .concat(DEBUG ? [] : [ 36 | new webpack.optimize.DedupePlugin(), 37 | new webpack.optimize.UglifyJsPlugin(), 38 | new webpack.optimize.AggressiveMergingPlugin() 39 | ]), 40 | 41 | module: { 42 | loaders: [ 43 | { test: /\.html$/, loader: 'html' }, 44 | { test: /\.json$/, loader: 'json' } 45 | ] 46 | }, 47 | 48 | resolve: { 49 | extensions: ['', '.js', '.jsx', '.json'], 50 | 51 | modulesDirectories: [ 52 | 'node_modules', 53 | 'app' 54 | ], 55 | 56 | root: path.join(__dirname, 'app'), 57 | 58 | alias: { 59 | durandal: 'durandal/js', 60 | plugins: 'durandal/js/plugins' 61 | } 62 | }, 63 | 64 | externals: { 65 | jquery: 'jQuery' 66 | }, 67 | 68 | devServer: { 69 | contentBase: __dirname, 70 | hot: false, 71 | inline: true, 72 | historyApiFallback: true, 73 | stats: { colors: true }, 74 | progress: true 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /dist/1.chunk.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([1],{ 2 | 3 | /***/ 34: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | var router = __webpack_require__(6); 7 | var ViewModel = __webpack_require__(4); 8 | 9 | var Index = new ViewModel({ 10 | view: __webpack_require__(45) 11 | }); 12 | 13 | Index.router = router.createChildRouter() 14 | .makeRelative({ fromParent: true }) 15 | .map( 16 | __webpack_require__(35) 17 | ) 18 | .buildNavigationModel(); 19 | 20 | module.exports = Index; 21 | 22 | 23 | /***/ }, 24 | 25 | /***/ 35: 26 | /***/ function(module, exports, __webpack_require__) { 27 | 28 | module.exports = [ 29 | { 30 | route: '', 31 | title: 'What?', 32 | moduleId: function() { 33 | return __webpack_require__(36); 34 | }, 35 | nav: true 36 | }, 37 | 38 | // We can nest async routes, too, allowing us to conviniently 39 | // separate out various parts of our application into distinct, 40 | // async loaded parts. Beautiful! 41 | { 42 | route: 'nested', 43 | title: 'One other thing...', 44 | moduleId: function(cb) { 45 | __webpack_require__.e/* require */(3, function(__webpack_require__) { /* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_REQUIRE_ARRAY__ = [__webpack_require__(37)]; (function(module) { 46 | cb(null, module); 47 | }.apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__)); 48 | /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(15)(module)))}); 49 | }, 50 | nav: true 51 | } 52 | ]; 53 | 54 | 55 | /***/ }, 56 | 57 | /***/ 36: 58 | /***/ function(module, exports, __webpack_require__) { 59 | 60 | var router = __webpack_require__(6); 61 | var ViewModel = __webpack_require__(4); 62 | 63 | var How = new ViewModel({ 64 | view: __webpack_require__(46) 65 | }); 66 | 67 | module.exports = How; 68 | 69 | 70 | /***/ }, 71 | 72 | /***/ 45: 73 | /***/ function(module, exports) { 74 | 75 | module.exports = "
\n\t

Router

\n\t

\n\t\tWe can interface nicely with the router component, too. You're seeing that in action now!\n\t

\n\t
\n\n\t
\n\t\t

How?

\n\t\t

\n\t\t\tTypically Durandals router module requires you to specify a moduleId\n\t\t\tfor each route.\n\t\t

\n\n\t\t

\n\t\t\tTo allow Webpack to know these are actual dependencies, we instead set each\n\t\t\tmoduleId to the actual ViewModel module instance instead, and override the core\n\t\t\tDurandal composition and system modules to handle it.\n\t\t

\n\t
\n\t
\n\n\t
\n\t\t

Asynchronous Routes!

\n\n\t\t
    \n\t\t\t
  • \n\t\t\t\t\n\t\t\t
  • \n\t\t
\n\n\t\t
\t\n\t
\n
"; 76 | 77 | /***/ }, 78 | 79 | /***/ 46: 80 | /***/ function(module, exports) { 81 | 82 | module.exports = "
\n\t

\n\t\tAlong with the ability to bundle all the routes together, we can also optionally bundle specific\n\t\tparts of the application together into chucks we can grab on-the-fly when the user goes to\n\t\tspecific routes!\n\t

\n\t

\n\t\tIn fact... this very page was requested separately from the main app.js.\n\t\tCheck the network tab to see this in action (look for the 1.chunk.js file)!\n\t\n\t

\n\t\tThis is great for large apps where we don't need or want the user to download the entirety of\n\t\tthe application code at once.\n\t

\n
"; 83 | 84 | /***/ } 85 | 86 | }); -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "globals": { 7 | "__DEV__": true, 8 | "process": true 9 | }, 10 | "rules": { 11 | "strict": false, 12 | "comma-dangle": false, 13 | "no-cond-assign": true, 14 | "no-constant-condition": true, 15 | "no-control-regex": true, 16 | "no-debugger": true, 17 | "no-dupe-args": true, 18 | "no-dupe-keys": true, 19 | "no-empty": true, 20 | "no-empty-class": true, 21 | "no-ex-assign": true, 22 | "no-extra-boolean-cast": false, 23 | "no-extra-semi": true, 24 | "no-func-assign": true, 25 | "no-inner-declarations": true, 26 | "no-invalid-regexp": true, 27 | "no-irregular-whitespace": true, 28 | "no-negated-in-lhs": true, 29 | "no-obj-calls": true, 30 | "no-regex-spaces": true, 31 | "no-sparse-arrays": true, 32 | "no-unreachable": true, 33 | "use-isnan": true, 34 | "valid-jsdoc": true, 35 | "valid-typeof": true, 36 | "block-scoped-var": true, 37 | "consistent-return": false, 38 | "curly": true, 39 | "dot-notation": true, 40 | "eqeqeq": true, 41 | "no-alert": true, 42 | "no-caller": true, 43 | "no-div-regex": true, 44 | "no-empty-label": true, 45 | "no-eq-null": true, 46 | "no-eval": true, 47 | "no-extend-native": true, 48 | "no-extra-bind": true, 49 | "no-fallthrough": false, 50 | "no-implied-eval": true, 51 | "no-iterator": true, 52 | "no-labels": true, 53 | "no-lone-blocks": true, 54 | "no-loop-func": true, 55 | "no-multi-spaces": true, 56 | "no-multi-str": true, 57 | "no-native-reassign": true, 58 | "no-new": true, 59 | "no-new-func": true, 60 | "no-new-wrappers": true, 61 | "no-octal": true, 62 | "no-octal-escape": true, 63 | "no-process-env": true, 64 | "no-proto": true, 65 | "no-redeclare": false, 66 | "no-return-assign": true, 67 | "no-script-url": true, 68 | "no-sequences": false, 69 | "no-unused-expressions": true, 70 | "no-void": true, 71 | "no-with": true, 72 | "radix": true, 73 | "wrap-iife": true, 74 | "yoda": false, 75 | "no-catch-shadow": true, 76 | "no-delete-var": true, 77 | "no-label-var": true, 78 | "no-shadow": true, 79 | "no-shadow-restricted-names": true, 80 | "no-undef-init": true, 81 | "no-unused-vars": true, 82 | "no-use-before-define": true, 83 | "indent": [ 84 | 2, 85 | "tab" 86 | ], 87 | "brace-style": [ 88 | 2, 89 | "stroustrup", 90 | { 91 | "allowSingleLine": true 92 | } 93 | ], 94 | "camelcase": true, 95 | "comma-spacing": [ 96 | 2, 97 | { 98 | "before": false, 99 | "after": true 100 | } 101 | ], 102 | "comma-style": [ 103 | 2, 104 | "last" 105 | ], 106 | "consistent-this": [ 107 | 0, 108 | "_this" 109 | ], 110 | "eol-last": true, 111 | "func-names": false, 112 | "key-spacing": [ 113 | 2, 114 | { 115 | "beforeColon": false, 116 | "afterColon": true 117 | } 118 | ], 119 | "new-cap": true, 120 | "new-parens": true, 121 | "no-array-constructor": true, 122 | "no-lonely-if": true, 123 | "no-mixed-spaces-and-tabs": true, 124 | "no-mixed-requires": false, 125 | "no-new-object": true, 126 | "no-spaced-func": true, 127 | "no-trailing-spaces": true, 128 | "no-underscore-dangle": false, 129 | "no-wrap-func": false, 130 | "quotes": [ 131 | 2, 132 | "single", 133 | "avoid-escape" 134 | ], 135 | "semi": [ 136 | 2, 137 | "always" 138 | ], 139 | "semi-spacing": [ 140 | 2, 141 | { 142 | "before": false 143 | } 144 | ], 145 | "space-after-keywords": false, 146 | "space-before-function-parentheses": false, 147 | "space-in-brackets": false, 148 | "space-in-parens": true, 149 | "space-infix-ops": true, 150 | "space-return-throw-case": true, 151 | "spaced-line-comment": [ 152 | "always" 153 | ] 154 | } 155 | } --------------------------------------------------------------------------------