问题
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']);
};
回答1:
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'
}
回答2:
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.
<div data-bind="compose: { model: someObject, view: '../../components/something/something.html' }"></div>
I ended up removing the relative path and just doing the following:
<div data-bind="compose: { model: someObject, view: 'components/something/something.html' }"></div>
With that, I was able to get everything with the optimized build working.
来源:https://stackoverflow.com/questions/23293185/using-r-js-to-package-a-spa-application-that-loads-views-using-text