Using r.js to package a SPA application that loads views using 'text'
almond, durandal, gruntjs, r.js, requirejs
Solution
Almond doesn't support dynamic loading. It doesn't implement `require.toUrl` and doesn't support asynchronous loader plugins. James Burke, the creator of RequireJS and Almond, answered about the same issue here.
To work around this, you can include RequireJS into the bundle instead of Almond. See an example here. You have to create an alias for `require.js` in the `paths` section of the r.js config as `require` is a special reserved dependency name. Then, specify this alias in the `name` field of the config instead of Almond. You'll get something like this:
options: {
name: 'requireLib', // use the alias
baseUrl: requireConfig.baseUrl,
mainPath: 'App/main',
paths: mixIn(
{},
requireConfig.paths,
{ 'requireLib': '../Scripts/require.js' }), // declare the alias
exclude: [],
inlineText: false,
optimize: 'none',
out: 'build/app/main-built.js'
}
Problem
I'm attempting to build a SPA application (requirejs, durandal 2, knockout) into a single main-build.js file using grunt, and I'm running into serious issues with the 'text' plugin that durandal is using to load my views. In dev, I'm successfully using 'text' to load views dynamically as per the standard way of building durandal apps. The difference is that I need to do some server side templating for the views and so they are actually being dynamically generated. With that in mind I'd like to use r.js to package the apps models, view models and services (via the grunt-durandal plugin) into a single file, but to NOT package the views (.html) and still load them dynamically as needed. In my grunt config, I'm using the `inlineText: false` option - which I have checked is suppressing the 'text!*' modules in the build. But when I run the application I'm getting: `Uncaught TypeError: undefined is not a function` from inside `text.load` on the following line: ``` var parsed = text.parseName(name), nonStripName = parsed.moduleName + (parsed.ext ? '.' + parsed.ext : ''), url = req.toUrl(nonStripName), // EXCEPTION THROWN HERE ``` The module name being loaded seems to be correct (its a 'text!*' one) but beyond that I have no idea how to proceed to debug this issue. What am I doing wrong? My grunt configuraiton is: ``` /*global module, require */ module.exports = function (grunt) { 'use strict'; // library that allows config objects to be merged together var mixIn = require('mout/object/mixIn'); var requireConfig = { baseUrl: 'App/', paths: { 'jquery': '../Scripts/jquery-2.1.0', 'knockout': '../Scripts/knockout-3.1.0', 'text': '../Scripts/text', 'durandal': '../Scripts/durandal', 'plugins': '../Scripts/durandal/plugins', 'transitions': '../Scripts/durandal/transitions', } }; grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), jshint: { options: { "-W099": true, // allowed mixed tabs and spaces "-W004": true, // needed to avoid errors where TS fakes inheritance ignores: [ 'App/main-built.js' // ingore the built/compacted file ] }, all: [ // run jshint on these files 'gruntfile.js', 'App/**/*.js' ] }, // TODO: could add jasmine here to do JS testing clean: { build: ['build/*'] }, copy: { scripts: { src: 'Scripts/**/**', dest: 'build/' } }, durandal: { main: { src: [ 'App/**/*.*', '!App/main-built.js', // ignore the built file! 'Scripts/durandal/**/*.js' ], options: { name: '../Scripts/almond-custom', baseUrl: requireConfig.baseUrl, mainPath: 'App/main', paths: mixIn( {}, requireConfig.paths, { 'almond': '../Scripts/almond-custom.js' }), exclude: [], inlineText: false, // prevent bundling of .html (since we are dynamically generating these for content) optimize: 'none', // turn off optimisations - uglify will run seperately by grunt to do this out: 'build/app/main-built.js' } } }, uglify: { options: { banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> \n' + '* Copyright (c) <%= grunt.template.today("yyyy") %> Kieran Benton \n' + '*/\n' }, build: { src: 'build/App/main.js', dest: 'build/App/main-built.js' } } } ); // loading plugin(s) grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-jasmine'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-open'); grunt.loadNpmTasks('grunt-durandal'); // only one grunt task grunt.registerTask('build', [ 'jshint', 'clean', 'copy', 'durandal:main']); }; ```