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']);
};
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'
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With