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']);
};
Just want to add some extra context to this question since I kept coming back to it for guidance and eventually solved my problem. I'm using Durandal w/ TypeScript and grunt for optimization. I was getting some exceptions around the text plugin, e.g.
I ended up removing the relative path and just doing the following:
With that, I was able to get everything with the optimized build working.
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 thepaths
section of the r.js config asrequire
is a special reserved dependency name. Then, specify this alias in thename
field of the config instead of Almond. You'll get something like this: