I have a JavaEE project that uses RequireJS to load a few third party frameworks. One of those frameworks is OpenLayers3. Openlayers3 natively creates a global "ol" variable. However, OpenLayers3 is written to be AMD compatible and works as a module through RequireJS. I also am using an OpenLayers3 plugin called "olLayerSwitcher" which is not optimized for AMD. Instead, it depends on the "ol" variable being global.
My require config looks like the following:
paths: {
"sinon": ['/webjars/sinonjs/1.7.3/sinon'],
"jquery": ["/webjars/jquery/2.1.4/jquery"],
"backbone": ['/webjars/backbonejs/1.2.1/backbone'],
"underscore": ['/webjars/underscorejs/1.8.3/underscore'],
"text": ['/webjars/requirejs-text/2.0.14/text'],
"log4js": ['/webjars/log4javascript/1.4.13/log4javascript'],
"ol": ['/webjars/openlayers/3.5.0/ol'],
"olLayerSwitcher": ['/js/vendor/ol3-layerswitcher/1.0.1/ol3-layerswitcher']
},
shim: {
"olLayerSwitcher": {
deps: ["ol"],
exports: "olLayerSwitcher"
},
'sinon' : {
'exports' : 'sinon'
}
}
The project is uses Backbone and includes a Router module (/src/main/webapp/js/controller/AppRouter.js):
/*jslint browser : true*/
/*global Backbone*/
define([
'backbone',
'utils/logger',
'views/MapView'
], function (Backbone, logger, MapView) {
"use strict";
var applicationRouter = Backbone.Router.extend({
routes: {
'': 'mapView'
},
initialize: function () {
this.LOG = logger.init();
this.on("route:mapView", function () {
this.LOG.trace("Routing to map view");
new MapView({
mapDivId: 'map-container'
});
});
}
});
return applicationRouter;
});
The Router module depends on a View module (/src/main/webapp/js/views/MapView.js):
/*jslint browser: true */
define([
'backbone',
'utils/logger',
'ol',
'utils/mapUtils',
'olLayerSwitcher'
], function (Backbone, logger, ol, mapUtils, olLayerSwitcher) {
"use strict";
[...]
initialize: function (options) {
this.LOG = logger.init();
this.mapDivId = options.mapDivId;
this.map = new ol.Map({
[...]
controls: ol.control.defaults().extend([
new ol.control.ScaleLine(),
new ol.control.LayerSwitcher({
tipLabel: 'Switch base layers'
})
])
});
Backbone.View.prototype.initialize.apply(this, arguments);
this.render();
this.LOG.debug("Map View rendered");
}
});
return view;
});
The View module attempts to pull in both OpenLayers3 as well as the third-party OpenLayers plugin.
When the project is built and deployed, it works fine in-browser. When the View module is loaded, OpenLayers and the third-party plugin are pulled in just fine and everything renders properly.
However, when I attempt to test this in Jasmine is where all of this falls apart.
For Jasmine, I am using the Jasmine-Maven plugin. It pulls in JasmineJS, PhantomJS and RequireJS along with my libraries and runs my specs. The issue is that when run via Jasmine, the MapView module attempts to load both the OpenLayers3 library as well as the third party plugin (olLayerSwitcher) but fails because the third party plugin can't find "ol".
The test:
define([
"backbone",
"sinon",
'controller/AppRouter'
], function (Backbone, sinon, Router) {
describe("Router", function () {
beforeEach(function () {
this.router = new Router();
this.routeSpy = sinon.spy();
this.router.bind("route:mapView", this.routeSpy);
try {
Backbone.history.start({silent: true});
} catch (e) {
}
this.router.navigate("elsewhere");
});
it("does not fire for unknown paths", function () {
this.router.navigate("unknown", true);
expect(this.routeSpy.notCalled).toBeTruthy();
});
it("fires the default root with a blank hash", function () {
this.router.navigate("", true);
expect(this.routeSpy.calledOnce).toBeTruthy();
expect(this.routeSpy.calledWith(null)).toBeTruthy();
});
});
});
The error from Jasmine:
[ERROR - 2015-08-08T21:27:30.693Z] Session [4610ead0-3e14-11e5-bb2b-dd2c4b5c2c7b] - page.onError - msg: ReferenceError: Can't find variable: ol
:262 in error
[ERROR - 2015-08-08T21:27:30.694Z] Session [4610ead0-3e14-11e5-bb2b-dd2c4b5c2c7b] - page.onError - stack:
global code (http://localhost:58309/js/vendor/ol3- layerswitcher/1.0.1/ol3-layerswitcher.js:9)
:262 in error
JavaScript Console Errors:
* ReferenceError: Can't find variable: ol
The relevant section from the ol3-layerswitcher plugin on line 9 is:
[...]
ol.control.LayerSwitcher = function(opt_options) {
[...]
So it does depend on "ol" being a thing at this point.
The Jasmine-Maven plugin creates its own spec runner HTML and the relevant portion looks like this:
<script type="text/javascript">
if(window.location.href.indexOf("ManualSpecRunner.html") !== -1) {
document.body.appendChild(document.createTextNode("Warning: Opening this HTML file directly from the file system is deprecated. You should instead try running `mvn jasmine:bdd` from the command line, and then visit `http://localhost:8234` in your browser. "))
}
var specs = ['spec/controller/AppRouterSpec.js'];
var configuration = {
paths: {
"sinon": ['/webjars/sinonjs/1.7.3/sinon'],
"jquery": ["/webjars/jquery/2.1.4/jquery"],
"backbone": ['/webjars/backbonejs/1.2.1/backbone'],
"underscore": ['/webjars/underscorejs/1.8.3/underscore'],
"text": ['/webjars/requirejs-text/2.0.14/text'],
"log4js": ['/webjars/log4javascript/1.4.13/log4javascript'],
"ol": ['/webjars/openlayers/3.5.0/ol'],
"olLayerSwitcher": ['/js/vendor/ol3-layerswitcher/1.0.1/ol3-layerswitcher']
},
shim: {
"olLayerSwitcher": {
deps: ["ol"],
exports: "olLayerSwitcher"
},
'sinon' : {
'exports' : 'sinon'
}
}
};
if (!configuration.baseUrl) {
configuration.baseUrl = 'js';
}
if (!configuration.paths) {
configuration.paths = {};
}
if (!configuration.paths.specs) {
var specDir = 'spec';
if (!specDir.match(/^file/)) {
specDir = '/'+specDir;
}
configuration.paths.specs = specDir;
}
require.config(configuration);
require(specs, function() {
jasmine.boot();
});
I am able to create a customer HTML runner but am not sure what the problem is so I wouldn't know what needs changing.
This doesn't seem to be a PhantomJS issue as I can load the tests in-browser and am experiencing the same issue.
I'd appreciate if anyone has any thoughts on what could be happening here. I really do not want to hack up the third-party module to transform it into a RequireJS module as the Jasmine testing is the last-leg of implementing this completely and I'm completely stuck here.
I am using Jasmine 2.3.0 and RequireJS 2.1.18
I apologize for not linking out more but this is a new account and I don't have enough rep for it.
It will be tough to figure out the problem without a running version of your setup.
However, if you're able to customize the SpecRunner.html for jasmine generated by the maven plugin, simply include the jasmine(/ any other library causing an issue) in the SpecRunner html - <script src="/<path_to_lib>">.
In my experience, its usually not worth the effort , to make libraries used in source amd compliant and play nicely with every other library for testing setup.
Related
I have a web app using RequireJS. Here is my js/main.js file:
require.config({
baseUrl: 'js/',
paths: {
jquery: 'libs/jquery/jquery',
lodash: 'libs/lodash/lodash',
backbone: 'libs/backbone/backbone',
// [other dependencies...]
}
});
require(['views/AppView'], function (AppView) {
var app_view = new AppView;
});
here is my js/views/AppView.js file:
define([
'jquery',
'underscore',
'backbone',
'joint',
'views/ProjectView',
'models/Command',
'views/detailsview',
'views/newcellview'
], function ($, _, Backbone, joint, ProjectView, Command, DetailsView, NewCellView) {
var app_view = {stub: 'stub'};
return app_view;
});
and finally here is my AppViewTest.js file which I run with mocha js/test/AppViewTest.js:
var assert = require('assert');
var requirejs = require('requirejs');
describe('AppView', function() {
var app_view;
beforeEach(function (done) {
requirejs(['../views/AppView.js'], function (AppView) {
app_view = new AppView;
});
});
it('should be [...]', function() {
assert.equal(app_view, ...);
});
});
I get the following error:
1) AppView views "before each" hook:
Uncaught Tried loading "jquery" at /usr/local/lib/node_modules/mocha/bin/jquery.js then tried node's require("jquery") and it failed with error: Error: Cannot find module 'jquery'
You start your test with:
var assert = require('assert');
var requirejs = require('requirejs');
and then you start making calls to requirejs but there's nothing in here that configures RequireJS.
If you cut and paste the require.config call you have in your main.js into your test file, that would configure your test for loading the same files as you have in your regular application.
Another way to do it would be:
before(function (done) {
requirejs(['full-path-to/main'], function () {
requirejs(['../views/AppView'], function (AppView) {
app_view = new AppView;
done();
});
});
});
The point is to load your configuration before you load your AppView. Note also that you need to call done, and I've changed beforeEach to before because there's no need to reload it before each test. (It was a slip of the mind earlier when I induced you to use beforeEach. My bad.)
I've also removed the .js from your requirejs call because you should not use .js. Sometimes it is warranted but unless you can explain why you need it, you should not use it.
I'm new to working with RequireJS, and am trying to figure out shimming 3rd-party, interdependent scripts. Specifically, I'm trying to get the Stanford Crypto scripts imported.
Basically, the suite is comprised of the core (jsbn.js, jsbn2.js, base64.js, rng.js, and prng4.js), a basic RSA script (rsa.js), and an extended RSA script (rsa2.js).
rsa.js defines the global variable-object RSAKey, and rsa2.js references it.
function RSAKey() {
this.n = null;
this.e = 0;
this.d = null;
this.p = null;
this.q = null;
this.dmp1 = null;
this.dmq1 = null;
this.coeff = null;
}
I've set up my shim in a way that I thought was correct, but I get the error "RSAKey is not defined" in rsa2.js. The following is my shim:
require.config({
paths: {
'jsbn': "../StanfordRSA/jsbn.js",
'jsbn2': "../StanfordRSA/jsbn2.js",
'base64': "../StanfordRSA/base64.js",
'rng': "../StanfordRSA/rng.js",
'prng4': "../StanfordRSA/prng4.js",
'rsa': "../StanfordRSA/rsa.js",
'rsa2': "../StanfordRSA/rsa2.js"
},
shim: {
'rsa': {
deps: ['jsbn', 'jsbn2', 'base64', 'rng', 'prng4'],
exports: "RSAKey"
},
'rsa2': {
deps: ['rsa']
}
}
});
My understanding, then, is that if I set 'rsa2' as a requirement in one of my RequireJS modules, it would look at the shim and see that rsa2 is dependent on rsa, which is dependent on the core and exports RSAKey...But that's not what's happening, and it seems like either rsa isn't loading, or it isn't loading correctly. (Please note that all of this works using raw script tags. I'm trying to convert an already existing, already functioning webapp to RequireJS)
Thoughts?
Your basic setup is correct, except for 2 things:
(really important!) You have to omit the .js extensions!!!
You probably have missed the exact dependencies between the scripts.
After some experimentation and reading the comments at the top of the scripts, the working configuration is:
require.config({
paths: {
'jsbn': "../StanfordRSA/jsbn",
'jsbn2': "../StanfordRSA/jsbn2",
'base64': "../StanfordRSA/base64",
'rng': "../StanfordRSA/rng",
'prng4': "../StanfordRSA/prng4",
'rsa': "../StanfordRSA/rsa",
'rsa2': "../StanfordRSA/rsa2"
},
shim: {
'rng': {
deps: ['prng4']
},
'jsbn2': {
deps: ['jsbn']
},
'rsa': {
deps: ['jsbn', 'rng'],
exports: 'RSAKey'
},
'rsa2': {
deps: ['rsa', 'jsbn2'],
exports: 'RSAKey'
}
}
});
Check out a plunk here.
I'm having an angular project bundled with browserify using Gulp. Here is the tree
|--app
|--src
--js
-main.js
-otherFiles.js
|--spec
--mainspec.js <-- jasmin spec file
|--public
--js
--main.js
I'm having a gulp file which takes my source, main.js file, and browserifies it along with a gulp-jasmine tasks
gulp.task('js', function() {
return gulp.src('src/js/main.js')
.pipe(browserify({}))
.pipe(gulp.dest('public/js'));
});
gulp.task('specs', function () {
return gulp.src('spec/*.js')
.pipe(jasmine());
});
Along with some watch tasks etc.
Now, in my mainspec.js file, angular is not recognized, considering my test code:
describe("An Angular App", function() {
it("should actually have angular defined", function() {
expect(angular).toBeDefined();
});
});
And I'm getting an ReferenceError: angular is not defined error on terminal. I tried to require('angular'); on the first line but with no luck, getting a new error ReferenceError: window is not defined. I know there is something wrong with the setup and the test file not being able to reach the browserified files, but I can't just figure out the solution.
Any ideas?
Thanks in advance.
You need to define all aspects in your config file
function getKarmaConfig(environment) {
return {
frameworks : ['jasmine'],
files : [
// Angular + translate specified for build order
environment + '/js/jquery.min.js',
environment + '/js/angular.js',
environment + '/js/angular-translate.min.js',
environment + '/js/**/*.js',
'bower_components/angular-mocks/angular-mocks.js',
'test/unit/**/*.js'
],
exclude : [
],
browsers : ['PhantomJS'],
reporters : ['dots', 'junit','coverage'],
junitReporter: {
outputFile: 'test-results.xml'
},
preprocessors : {
'prod/js/*.js': ['coverage']
},
coverageReporter:{
type: 'html',
dir: 'coverage'
}
};
};
and define a gulp test task like this
gulp.task('test', ['build_prod'], function () {
var testKarma = getKarmaConfig(environment);
testKarma.action = 'run';
testKarma.browsers = ['PhantomJS'];
return gulp.src('./fake')
.pipe(karma(testKarma));
});
You just need to define src perfectly as per your structure. This will work :)
I'm writing a modular web app using RequireJS for module loading and dependency injection.
From my bootstrap.js file I load Application.js and initialize it, passing in an array of modules that are to be "loaded"(1) by the Application. When the initialization finishes, the Application will call a function to signal that it's done loading.
I'm loading the modules asynchronously (in regard to each other) using require(["module"], callback(module), callback(error)).
The problem that I'm having with this is that the error callback (errback) is not called when a module fails to load (at least on Chrome when the server responds with a 404 status code).
I can see the error in the Google Chrome Developer Tools Console, but the errback isn't called:
GET http://192.168.1.111:8812/scripts/modules/InexistentModule/manifest.js 404 (Not Found)
Uncaught Error: Load timeout for modules: modules/InexistentModule/manifest
Has anyone experienced gotten around this issue with RequireJS errbacks? If so, how?
(1) Actually, I'm just loading module manifests, not the entire modules, so that I can display icons for them and register their routes using Backbone.SubRoute
The libraries that I'm using (none of them are minified):
RequireJS 1.0.8
Underscore 1.6.0
jQuery 1.11.0
Backbone 1.1.2
Backbone.SubRoute 0.4.1
Out of the libraries above, only RequireJS and Underscore are used directly by me at the moment.
I've used Underscore for currying when passing the success/failure callbacks to Require in order to pass in the i from my loop as the index parameter. For the success callback, this works wonderfully and I think that this does not affect the errback (I've tested with a simple function of arity 1, instead of the partial function returned by _.partial and the function is still not called in case of a 404 error).
I'll post my bootstrap.js and Application.js files here as they might provide more info on this.
Thank you!
bootstrap.js
require.config({
catchError: true,
enforceDefine: true,
baseUrl: "./scripts",
paths: {
"jquery": "lib/jquery",
"underscore": "lib/underscore",
"backbone": "lib/backbone",
"backbone.subroute": "lib/backbone.subroute"
},
shim: {
"underscore": {
deps: [],
exports: "_"
},
"backbone": {
deps: ["jquery", "underscore"],
exports: "Backbone"
},
"backbone.subroute": {
deps: ["backbone"],
exports: "Backbone.SubRoute"
}
}
});
define(["jquery", "underscore", "backbone", "Application"],
function ($, _, Backbone, Application) {
var modules = ["Home", "ToS", "InexistentModule"];
var defaultModule = "Home";
var onApplicationInitialized = function()
{
require(["ApplicationRouter"], function(ApplicationRouter){
ApplicationRouter.initialize();
});
}
Application.initialize(modules, defaultModule, onApplicationInitialized);
}
);
Application.js
define([
'jquery',
'underscore',
'backbone'],
function($,_,Backbone){
var modules;
var manifests = [];
var routers = [];
var defaultModule = "";
var totalModules = 0;
var loadedModules = 0;
var failedModules = 0;
var onCompleteCallback = function(){};
var onModuleManifestLoadComplete = function(index, manifest){
manifests[index] = manifest;
console.log("Load manifest for module: " + modules[index] + " complete");
//TODO: init module
loadedModules++;
if(totalModules == (loadedModules + failedModules))
onCompleteCallback();
};
var onModuleManifestLoadFailed = function(index, err){
console.log("Load manifest for module: " + modules[index] + " failed");
failedModules++;
if(totalModules == (loadedModules + failedModules))
onCompleteCallback();
};
var initialize = function(_modules, _defaultModule, callback){
defaultModule = _defaultModule;
modules = _modules;
manifests = Array(modules.length);
totalModules = modules.length;
onCompleteCallback = callback;
for(i=0; i<modules.length; i++){
require(['modules/'+modules[i]+'/manifest'],
_.partial(onModuleManifestLoadComplete, i),
_.partial(onModuleManifestLoadFailed, i));
};
};
return {
modules: modules,
manifests: manifests,
routers: routers,
defaultModule: defaultModule,
initialize: initialize
};
});
You indicate you are using RequireJS 1.0.8. I've checked the documentation for the 1.x series and find nothing about errbacks. This page actually indicates that errbacks were introduced in the 2.x series.
Also the shim is something that was introduced in the 2.x series. So right now, RequireJS is ignoring your shims.
I need some help with the concept of only loading modules when they are needed using requireJS
this is my main.js
require(['jquery', 'path/somemodule'],
function($, somemodule) {
$(document).ready(function() {
somemodule.init()
})
})
and in the somemodule.js
define(['jquery', 'path/someothermodule'], function ($, someothermodule) {
"use strict";
var somemodule;
somemodule = {
init: function () {
someothermodule.init()
}
}
return somemodule;
)}
right now somemodule.js and someothermodule.js is loaded on all pages. How do I only load it when it's needed?
When you require a module2 from module1 using the standard define() syntax module1 will not load/run until module2 has been fully loaded. That looks like this:
// inside module1
define(['module2'], function(mod2) {
// we don't get here until AFTER module2 has already been loaded
});
An alternative to lazy-load module2 looks like this:
// inside module1
define([], function() {
require(['module2'], function(mod2) {
// we don't get here until AFTER module2 has already been loaded
});
// but we DO get here without having loaded module2
});
Now you have to program somewhat carefully to make sure you don't run into any issues with asynchronicity.
In your case you can modify your main.js file
require(['jquery'],
function($) {
// jquery is loaded, but somemodule has not
if(thisPageNeedsSomeModule) {
require(['path/somemodule'],
function(somemodule) {
// now somemodule has loaded
$(document).ready(function() {
somemodule.init()
})
});
}
})
Your main.js file will load any file paths provided to it, so long as other elements of your application specify them as dependencies. See my example main.js file:
require.config({
paths: {
'app': 'app',
'underscore':'bower_components/underscore/underscore-min',
'backbone':'bower_components/backbone/backbone-min',
'marionette':'bower_components/backbone.marionette/lib/backbone.marionette.min',
'jquery': 'bower_components/jquery/jquery.min',
'tpl':'bower_components/requirejs-tpl/tpl',
'bootstrap':'bower_components/bootstrap/dist/js/bootstrap.min',
'leaflet':'bower_components/leaflet/leaflet',
'leaflet.markercluster':'bower_components/leaflet/leaflet.markercluster',
},
shim: {
'underscore': {
exports: '_'
},
'leaflet': {
exports: 'L'
},
'leaflet.markercluster': {
deps: ['leaflet']
},
'backbone': {
deps: ['underscore']
},
'marionette': {
deps: ['backbone']
},
'jquery': {
exports: '$'
},
'bootstrap': {
deps: ['jquery']
},
'app': {
deps: ['jquery', 'leaflet','bootstrap', 'leaflet.markercluster', 'marionette', 'tpl']
},
'app.elem': {
deps:['app']
},
'app.api': {
deps:['app']
}
}
})
require(['app','app.api','app.elem'], function() {
App.start();
})
And my initial application file:
define(['router', 'collections/moments'], function(router, momentCollection) {
// Boot the app!
App = new Marionette.Application();
App.LocResolve = false; // Have we resolved the user's location?
App.Locating = true; // Are we actively tracking the user's location?
App.FileReader = window.FileReader ? new FileReader : null;
App.Position = null; // Instant access to Lat & Lng of user.
App.MomentsRaw = null; // Keep cached copy of returned data for comparison.
App.Moments = new momentCollection; // Current collection of moments.
App.Markers = new L.MarkerClusterGroup(); // Create Marker Cluster Group
App.View = null; // Current view.
// Marionette Regions
App.addRegions({
header: '#header',
map: '#map',
list: '#list',
modal: '#modal',
});
return App
})
I noticed that you aren't passing in a configuration object - is this intentional? If you use R.js, the build optimizer, it will automatically remove unused vendor files for you.
In short, sets paths to your vendor files in the require.js config, then call upon them via define() whenever you need a particular asset. This will ensure that only files you need are used. Hope this helps!