RequireJS is great for that, but I had considerable trouble getting it to work with WinJS. When I did get it working I then had trouble getting it to work properly.
RequireJS is expected to be initialised with an include reference in the page html with a data-main tag, but this doesn't really work with the WinJS app lifecycle, so it took a bit of work to get it behaving the way I expected.
Eventually I got it working, and working with async initialisation code which was a key part of the initialisation process in my sample app.
So the basics of getting RequireJS working in WinJS is the following - I have probably made a million big JavaScript no-no's here, but as I said, still learning.
default.html - add
<script src="/js/require.js" ></script>before default.js
default.js - initialise requireJS and include whatever dependencies you want to use in your startup code.
app.addEventListener("activated", function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
} else if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.closedByUser) {
// TODO: This application has been launched after a requested shutdown. Initialize
// your application here.
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
}
if (app.sessionState.history) {
nav.history = app.sessionState.history;
}
//RequireJS Initialisation here
require.config({
baseUrl: '/js'
});
//this creates a promise that calls complete() when the initAsync promises finish
//all this code happens within the require context so we are guaranteed that the dependencies are resolved before the promise is resolved
//the initAsync methods return promises that load data from internal json data files
//persistence.loadState is synchronous code so doesn't return a promise
var loadPromise = new WinJS.Promise(function (complete) {
require([
'persistence',
'game/staticdata/itemClassStore',
'game/staticdata/itemStore',
'game/gamestate/world',
'game/gamestate/characters',
'game/classes/character'
], function (persistence, itemClassStore, itemStore, world, characters, character) {
itemClassStore.initAsync()
.then(function () { return itemStore.initAsync(); })
.then(
function () {
var data = persistence.loadState();
world.fromJson(data);
complete();
}
);
});
});
//args.setPromise is a function on the activate event which allows you to wait for promises to complete before continuing
args.setPromise(
loadPromise
.then(function () { return WinJS.UI.processAll(); })
.then(
function () {
if (nav.location) {
nav.history.current.initialPlaceholder = true;
return nav.navigate(nav.location, nav.state);
} else {
return nav.navigate(Application.navigator.home);
}
}
)
);
}
});
xPage.js - resolve your dependencies in the page ready function with a call to require(), and perform your page initialisation as per normal.
////// /// /// /// (function () { "use strict"; WinJS.Namespace.define("CharacterSelect", { CharacterSelectViewModel: WinJS.Class.define( //the viewmodel constructor takes the injected requireJS dependency from the page ready code. function (world) { this._charactersModule = world.characters; this._itemsDataSource = new WinJS.Binding.List(this._charactersModule.characters); this._inventoryDataSource = new WinJS.Binding.List(null); }, { _charactersModule: null, _itemsDataSource: null, _inventoryDataSource: null, _selectedCharacterId: 0, selectedCharacterId: { get: function () { return this._selectedCharacterId; }, set: function (value) { this._selectedCharacterId = value; if (value == 0) { this._inventoryDataSource = new WinJS.Binding.List(null); } else { this._inventoryDataSource = new WinJS.Binding.List(this._charactersModule.get(value).inventory.items); } this._getObservable().notify("selectedCharacterId", value); this._getObservable().notify("inventoryDataSource", this._inventoryDataSource); } }, listDataSource: { get: function () { return this._itemsDataSource; } }, inventoryDataSource: { get: function () { return this._inventoryDataSource; } }, addNew: function () { WinJS.Navigation.navigate("/pages/newCharacter/newCharacter.html"); }, deleteCharacter: function (that) { that._charactersModule.deleteCharacter(this.selectedCharacterId); that._itemsDataSource = new WinJS.Binding.List(this._charactersModule.characters); that._getObservable().notify("listDataSource", that._itemsDataSource); } }, {}), }); WinJS.UI.Pages.define("/pages/characterSelect/characterSelect.html", { // This function is called whenever a user navigates to this page. It // populates the page elements with the app's data. ready: function (element, options) { require( [ 'persistence', "appbar", "game/gamestate/world" ], function (persistence, appbar, world) { //normal page initialisation code - all dependency modules are initialised at this point //create view model (passing in resolved dependencies) and bind to relevant events //(WinJS only has 1-way bindings and can't declaratively bind to button events that i can see) var section = element.querySelector("section"); var viewModel = new CharacterSelect.CharacterSelectViewModel(world); var observableviewModel = WinJS.Binding.as(viewModel); WinJS.Binding.processAll(section, observableviewModel); document.getElementById("cmdCreate").addEventListener("click", observableviewModel.addNew, false); document.getElementById("cmdDeleteCharacter").addEventListener( "click", function(){ observableviewModel.deleteCharacter(observableviewModel); persistence.saveState(); } , false ); document.getElementById("characters").winControl.onselectionchanged = function (ev) { var selection = document.getElementById("characters").winControl.selection; if (selection.getItems()._value.length > 0) { viewModel.selectedCharacterId = selection.getItems()._value[0].data.id; appbar.characterSelectSelected(); } else { viewModel.selectedCharacterId = 0; appbar.characterSelectDeSelected(); } }; appbar.characterSelectInit(); } ); }, unload: function () { // TODO: Respond to navigations away from this page. }, updateLayout: function (element, viewState, lastViewState) { /// // TODO: Respond to changes in viewState. } }); })();