Durandal implements a process known as composition, which allows developers to compose arbitrary Views and ViewModels together into a single output. Much like how React lets you nest Components within your
render method. This
composition workflow is used extensively in Durandal and is the process by which your
View(s) are stitched together to create the basic building blocks of your application.
By default, the
composition engine deals with what are known in Durandal as
viewUrls, which are URLs that point to where the
View are situated, respectively. Since Webpack doesn't understand this natively, we'll need to enhance this functionality a little and allow us to directly pass the module, rather than a path pointing to it.
The logic for this method is fairly straight forward, taking a single
moduleId string and returning a Promise instance that resolves with the
We'll override this method to allow the
moduleId to be either a
String or a
Function. In the latter case, this function is given a single argument, another function we'll call
cb, which takes the following form:
cb function should be called with
null as the first argument if no error occurred, and the resolved
module as the second parameter. This may look familiar to you if you've used Node extensively, as it's the standard Node.js callback pattern.
With this in mind, let's take a look at the override in full:
This may look complex, but basically all it's allowing you to do is specify
moduleId as a
Function, which can either return the
module directly, or execute a passed callback when you've retrieved it asynchronously (more on why we want this later!).
The logic for this method is a little more complex, but we're only interested in the first parameter:
We'll need to override this method to allow the
viewOrUrlOrId to be a bare HTML string (a
String whose first character is a
First, we check the
viewOrUrlOrId parameter to see if it's both a string and starts with a
<. If it does, we assume it's actually a view as opposed to a
viewUrl, and return it as a resolved Promise. If it doesn't, we simply proceed with the normal Durandal functionality, which helps us maintain backwards compatibility.
A note on these enhancementsYou'll need to include these enhancements before your
app.setRoot()call, to ensure any calls are intercepted by the new functionality. Otherwise, things may not work as expected
Now we've got our new functionality in place via our enhancements, let's see a practical example of how to use this new functionality with Durandal, so Webpack can bundle our
Normally, you wouldn't need to specify the
View for a particular
ViewModel instance, as it's inferred by convention by Durandal, depending on the view strategy you've got set up. We'll need to make things more explicit if we want Webpack to bundle our views, however.
We can specify our views as explicit dependencies directly on the ViewModel instances by setting our
getView method to one which returns our
required view HTML:
Notice we are using the built-in
viewEngine.processMarkup method here. Durandal expects the output of a
getView call to be a
DOMElement instance, so we'll delegate this parsing operation to Durandal since it's already included and handles it nicely.
That's it. Together with our enhancements this is all that's needed to allow both Durandal and Webpack to understand the inter-dependency of our
ViewModel. Durandal composes just like before, and Webpack can bundle our view together with our app. Simples!
composition Binding Handler
compose binding handler can take various forms, as explained in the Durandal documentation. A sample of these is given below
Since we're not using Require.js, we'll no longer pass
viewUrls to the
compose binding, and will instead always pass a module or HTML instead, respectively. We'll store these as properties on the
require to retrieve them as static dependencies. For example, amending the previous code sample:
Notice how the dependencies for this
ViewModel are immediately visible by simply viewing the
viewModel.js file. This is a helpful by-product of this approach and should help you quickly review the top-level dependency graph of your
ViewModel without the need to look over your view file.
We've implemented some enhancements to the core Durandal
viewLocator.locateView methods which allow us to pass
view HTML directly to the
composition engine. This lets us specify our dependencies directly on the ViewModel, and allows Webpack to understand and bundle the different parts of our application together in an effective way.