I thought I'd post this as I stumbled around for a while before noticing what's going on. I have a test suite that uses CouchDB as its logging / recording database. I discovered you can write custom reporters in intern, so thought I could move a lot of my manual 'recordSuccess()'/'recordFailure()' calls out of my test script, and into a custom reporter responding to test pass and fail events.
My main test script still wants to do a little couchdb interaction, so I factored out the couchdb connection and reporting functions into a module, then tried to use that module from both the main test script, and the custom reporter module.
I find that the couchdb helper module is instantiated twice. This goes against the expectation that AMD/RequireJS require() will only execute a module once, and cache the result for use the next time the module is required. If I put a 'debugger' statement in its main body of code, it is clearly executed twice. The upshot, for me, is that the couchdb reference is undefined when called from the reporter.
Directory structure:
runTest.js # helper script to run intern test from this dir
src/MainTest.js
src/CouchHelper.js
src/CouchDBReporter.js
src/intern.js # intern config
runTest.js
node node_modules/.bin/intern-client config=src/intern suites=mypackage/WINTest --envConfig=src/test/dev.json
i.e. MainTest.js:
define([ 'CouchHelper' ], function (CouchHelper) {
.. test startup ..
CouchHelper.connect(username, password, etc);
CouchDBReporter.js:
define([ 'CouchHelper' ], function (CouchHelper) {
return {
'/test/fail': function (test) {
// Presume the couchdb is connected at this point
CouchHelper.recordFailure(test);
}
}
intern.js:
... blah blah ..
loader: {
// Packages that should be registered with the loader in each testing environment
packages: [
'node',
'nedb',
'nodemailer',
{ 'mypackage', 'src' }
],
reporters: [ 'console', 'src/CouchDBReporter' ]
CouchHelper.js:
define([
'intern/dojo/node!node-couchdb'
], function (Couchdb) {
debugger; // this is hit twice
var instance = 0;
function CouchHelper() {
this.couchdb = undefined;
this.instance = instance++;
console.log('Created instance ' + this.instance);
}
CouchHelper.prototype = {
connect: function () { this.couchdb = Couchdb.connect(blah); },
recordFailure: function (test) { this.couchdb.insert(blah); }
}
}
On startup, the console logs:
Created instance 0
Created instance 0
When the reporter calls recordFailure, it calls into a different instance of CouchHelper than the MainTest.js file called connect() on .. so this.couchdb is undefined, and the script crashes. I can call recordSuccess/recordFailure from in MainTest.js just fine, and this.couchdb is valid in CouchHelper, but from the CouchDBReporter the CouchHelper instance is clearly different.
Is this behaviour expected, and if so, what's the recommended way to share data and resources between the main test code, and code in a custom reporter? I see that in 3.0 the reporters config can take an object which might help mitigate this problem, but it feels like one would have to instantiate the reporter programatically rather than define it in config.
Nick
As suggested by Colin, the path to the answer lay in my loader map configuration. This means that my intern.js file, referenced as config on the command line, has a loader section where one can define the mappings of paths to AMD module (see https://theintern.github.io/intern/#option-loader). Typically I just define a list of package names, for example I know my test requires nedb, nodemailer, and my own src package:
loader: {
packages: [ 'node', 'nedb', 'nodemailer', 'src' ]
}
For some reason, I had defined my src package as being available by the name mypackage:
loader: {
packages: [ 'node', 'nedb', 'nodemailer',
{ name: 'mypackage', location: 'src' }
]
}
I had no good reason to do this. I then specified my custom reporter be loaded by intern using the 'src' package name:
intern.js:
reporters: [ 'console', 'src/CouchDBReporter' ]
And, here's the tricky bit, I referenced my helper module, CouchHelper, in two different ways, but both times by using a relative module path ./CouchHelper:
MainTest.js:
require([
'./CouchHelper',
...
], ...
CouchDBReporter.js:
require([
'./CouchHelper',
...
], ...
And on the command line, you guessed it, specified the test to be run as mypackage/MainTest.js. This conflicts with my specification of src/CouchDBReporter in intern.js's reporter section.
The result was that mypackage/MainTest.js required ./CouchHelper which resolved as mypackage/CouchHelper, and src/CouchDBReporter required ./CouchHelper, which resolved as src/CouchHelper. This loaded the CouchHelper module code twice, working around the usual guarantee with an AMD style loader that a module is only ever loaded once.
It has certainly been a good lesson in AMD module paths, and one implication of using relative paths.
Related
I have created a multiple pages web applications, including:
- a single requirejs configuration file, requestjsConfig.js
- some libraries, like jquery.js, etc...
- pages js, like homePage.js
- the form event binding js, like pageHeader.js
- common logical handling, like shoppingCart.js
I tried to use r.js to uglify my scripts, and merged into 1 js files. In general, it works, but with a small issues. After compiled, the browser still loads the form binding js files, event they are still merged into the page JS file.
requireJSConfig.js
requirejs.config({
baseUrl: 'frontjs/lib',
paths: {
eventHandler: '../page/eventHandler',
feature: '../feature'
}
});
homePage.js
requirejs(['../page/requestjsConfig'], function (requestjsConfig) {
//requirejs(['feature/page-header', 'feature/common', 'feature/index', 'feature/transport', 'feature/utils']);
requirejs(['eventHandler/pageHeader']);
});
pageHeader.js
define(function (require) {
var $ = require('jquery');
require('jquery-cookie');
require('jquery-storageapi');
var commonString = require('feature/commonString');
var shoppingCart = require('feature/shoppingCart');
...
}
the build.js for r.js to optimize
appDir: '../ecomm',
mainConfigFile: '../ecomm/frontjs/page/requestjsConfig.js',
dir: '../ecomm-built',
modules: [
//First set up the common build layer.
{
//module names are relative to baseUrl
name: '../page/requestjsConfig',
//List common dependencies here. Only need to list
//top level dependencies, "include" will find
//nested dependencies.
include: ['jquery',
'jquery.md5'
]
},
{
//module names are relative to baseUrl/paths config
name: '../page/homePage',
include: ['../page/eventHandler/pageHeader'],
exclude: ['../page/requestjsConfig']
},
{
//module names are relative to baseUrl/paths config
name: '../page/subCategory',
include: ['../page/eventHandler/pageHeader'],
exclude: ['../page/requestjsConfig']
}
]
}
After run node tools/r.js -o tools/build.js,
and when access home page, the pageHeader.js is still required.
I made it work, thanks everyone, the root cause is path.
In homePage.js, the module is defined as
requirejs(['eventHandler/pageHeader']);
In build.js,the module is defined as
include: ['../page/eventHandler/pageHeader'],
Project Structure
root
wwwroot <-- files under this location are static files public to the site
css
lib
bootstrap/js/bootstrap.js
jquery/js/jquery.js
knockout/knockout.js
requires/require.js
scripts
modules ┌───────────────┐
global.js <--│ Built modules │
dropdown.js └───────────────┘
modules
global.js ┌────────────────┐
dropdown <--│ Source modules │
dropdown.js └────────────────┘
gruntfile.js
global.cs Contents (pre-built version at ~/modules/global.js)
require.config({
baseUrl: "scripts/modules",
paths: {
jquery: "../../lib/jquery/js/jquery",
bootstrap: "../../lib/bootstrap/js/bootstrap",
knockout: "../../lib/knockout/knockout"
},
shims: {
bootstrap: {
deps: ['jquery']
}
},
});
define(function (require) {
var $ = require('jquery');
var ko = require('knockout');
var bootstrap = require('bootstrap');
});
dropdown.js Contents (pre-built version at ~/modules/dropdown.js)
define(function () {
console.log('dropdown initialized');
return 'foo';
});
HTML Page
Contains this script tag in the <head> of the page for loading requires config:
<script src="~/lib/requirejs/require.js" data-main="scripts/modules/global"></script>
In the body of the HTML page, I have the following:
<script>
require(['global'], function () {
require(['dropdown'], function (dropdown) {
console.log(dropdown);
});
});
</script>
Issue
The dropdown callback is undefined instead of the expected "foo" string that I'm returning from the defined module.
In fact, the console does not contain a log item for "dropdown initialized" either. This makes me believe the module is not being invoked somehow? However, it's strange the dropdown.js is present in F12 debugger as a script loaded into the page. Therefore, requires did make a call to load it, but did not run the contents of the define?
Noteworthy mentions
I'm using r.js to optimize and build. Both global.js and dropdown.js are processed over.
The name assigned to the dropdown module by r.js processing is "modules/dropdown/dropdown.js". I'm unsure if I should be using this somehow, or if I'm referring to the module correctly as just dropdown and relying on my baseUrl config having the correct path.
Edit #1
I have added the r.js build configuration used with grunt per commenter request. In conjunction, I updated the file structure to include the overall project structure, instead of just the runtime public wwwroot structure.
The r.js process will compile built forms of global.js + other modules in ~/wwwroot/scripts/modules from the source location ~/modules in summary.
function getRequireJsConfiguration() {
var baseUrl = './';
var paths = {
jquery: "wwwroot/lib/jquery/js/jquery",
bootstrap: "wwwroot/lib/bootstrap/js/bootstrap",
knockout: "wwwroot/lib/knockout/knockout"
};
var shims = {
bootstrap: {
deps: ['jquery']
}
};
var optimize = 'none';
var configuration = {};
var jsFilePaths = grunt.file.expand('modules/**/*.js');
jsFilePaths.forEach(function (jsFilePath) {
var fileName = jsFilePath.split('/').pop();
if (configuration[fileName]) {
throw 'Duplicate module name conflict: ' + fileName;
}
configuration[fileName] = {
options: {
baseUrl: './',
name: jsFilePath,
out: 'wwwroot/scripts/modules/' + fileName,
paths: paths,
shims: shims,
optimize: optimize,
exclude: ['jquery', 'knockout', 'bootstrap']
}
};
});
configuration['global'] = {
options: {
baseUrl: './',
name: 'modules/global.js',
out: 'wwwroot/scripts/modules/global.js',
paths: paths,
shims: shims,
optimize: optimize,
}
};
return configuration;
}
Edit #2
Thought it'd be a good idea to include the versions of requirejs packages I'm using:
requirejs: 2.1.15
grunt-contrib-requirejs: 0.4.4
Thanks.
The name assigned to the dropdown module by r.js processing is "modules/dropdown/dropdown.js". I'm unsure if I should be using this somehow, or if I'm referring to the module correctly as just dropdown and relying on my baseUrl config having the correct path.
In a sense, yes, you should be using that full path. That's what Require refers to as the module id - "modules/dropdown/dropdown" (if the .js in the above output was real, I suggest stripping that extension in the "name" config. .js is assumed by RequireJS, you don't want that string in your module ids). The basePath is used, when given IDs, to transform some unknown ID to a file path (e.g. 'bootstrap' id -> (applying path config) -> '../../lib/bootstrap/js/bootstrap' -> (applying base URL) -> 'scripts/modules/../../lib/bootstrap/js/bootstrap').
Really, though, just allowing r.js to concatenate everything into one file
is the preferred way to go. You could use the include option to include modules un-referenced by global.js in with the optimized bundle, too ( https://github.com/jrburke/r.js/blob/master/build/example.build.js#L438 )
As to your specific problem: your lazy require(['dropdown']) call is misleading you. By combining the requested module id with the basePath, RequireJS comes up with the URL you want - scripts/modules/dropdown - which defines a module with the module id scripts/module/dropdown - but since you requested the module id dropdown, you get nothing. (I would've guessed you'd get a RuntimeError instead of undefined, but I suppose that's how things go). One way or another you need to address the id/path mismatches.
Although I have resolved my issue with the hints wyantb's answer provided, I've since changed my approach to a single file concat due to the simplicity it brings. I still wanted to post the specifics of how I solved this question's issue for anyone else to happens along it.
In the grunt build configuration options, I added the onBuildWrite field to transform the content, so my assigned module IDs lined up with how I was lazily loading them.
onBuildWrite: function (moduleName, path, contents) {
return contents.replace(/modules\/global.js/, 'global');
}
This code is specifically for the global.js file. I implemented a similar onBuildWrite for the other module files (in the foreach loop). The transformation will essentially strip the path and extension from the module name that r.js assigns.
Here are some examples of before and after:
Before After
/modules/global.js global
/modules/dropdown/dropdown.js dropdown
/modules/loginButton/loginButton.js loginButton
Therefore, when I load the modules using the HTML script from my original question, requirejs resolves and finds a match.
Either require by path or define global and dropdown in global.cs
require(['./global'], function () {
require(['./dropdown'], function (dropdown) {
console.log(dropdown);
});
});
I have some module like this:
define('hello',[],
function()
{
return {
say: function(word) { console.log("Hello, "+word) },
};
});
And I'm using it like this (without any require.config) :
require(["hello"],
function(hello)
{
console.log("main",hello);
hello.say("main");
});
So far, so good.
But when I'm trying to require the same module with an absolute path, I've got my dependence module undefined:
require(["http://example.com/js/hello.js"],
function(hello)
{
console.log("main",hello);
hello.say("main");
});
Console:
main undefined
Uncaught TypeError: Cannot read property 'say' of undefined // Oops!
Why is it so?
Named module (define("NAME", [ ... ], function() { ... })) is meant to be required under exact that name. When it is required with a URL it is loaded correctly, but registers itself under its "desired" name, after which requirejs loses track of it still looking for a module with a name http://example.com/js/hello.js.
The reason the name given to define() isn't overriden with the one under which the module was required is to allow multiple module definitions to coexist in the file, for example after optimization. Optimizer will convert all define calls to the form with explicit name.
The reason the absolute name isn't converted to a module id is that this conversion is impossible. All the configuration options of requirejs determine how to convert module id to script location, not other way around.
Documentation discourages use of the named modules:
These are normally generated by the optimization tool. You can explicitly name modules yourself, but it makes the modules less portable ... It is normally best to avoid coding in a name for the module and just let the optimization tool burn in the module names...
Anonymous module, i.e.:
define([],
function()
{
return {
say: function(word) { console.log("Hello, "+word) },
};
});
works with either module id ("hello") or absolute path, because it is first registered without a name and later receives the name under which it was required.
I have a function in moduleA that must run prior to the loading of moduleB. ModuleA isn't dependent on any other module and moduleB has some dependencies (moduleC for instance). The following code does it and works fine when it's not optimized:
main-config.js
require.config({
paths: {
moduleA: 'modules/moduleA',
moduleB: "modules/moduleB",
moduleC: "modules/moduleC",
}
});
require(['moduleA'], function (moduleA) {
moduleA.init(function () {
require(['moduleB'], function (moduleB) {
moduleB.start();
});
});
});
However, when optimizing it with r.js, things are getting messed. The output of the r.js optimizer is:
Tracing dependencies for: ../scripts/main-config
Uglifying file: C:/.../scripts/main-config.js
C:/.../scripts/main-config.js
----------------
C:/.../scripts/libs/require/require.js
C:/.../scripts/modules/moduleA.js
C:/.../scripts/main-config.js
Which means that only the 3 modules - require, moduleA and main-config - are uglified together to 1 minimized file. All of moduleB's dependencies (such as moduleC) are missing from the output file.
Changing the config file to the following, will include all of moduleB's dependencies in the output file, but it will not get the needed result since it parses moduleB before moduleA's init function:
require(['moduleA','moduleB'], function (moduleA, moduleB) {
moduleA.init(function () {
moduleB.start();
});
});
I want moduleB to be parsed later, only after moduleA's init function (moduleB contains some immediate functions).
How can I get all the dependency tree to be included in the result file, but with my needed behavior (parse&run moduleB after a function of moduleA finishes)?
Thanks.
This happens because require for moduleB is nested and potentially dynamic; by default r.js won't include such dependencies in the output. To override this default behaviour you can set findNestedDependencies to true (more details in the example buildconfig file).
Alternatively, if you don't want to change this flag for the whole project and you only want to make an exception for this single dependency, you can add the module element to your buildconfig:
modules: [{
name: "main-config",
// forces moduleB to be included
include: ["moduleB"]
},
// ...
One way is with this little project I wrote: require-lazy
With this you would do:
require(['moduleA','lazy!moduleB'], function (moduleA, lazyModuleB) {
moduleA.init(function () {
lazyModuleB.get().then(function(moduleB) {
moduleB.start();
});
});
});
In order to use require-lazy, you will need to modify your building process a bit, see the examples (simple or grunt/bower).
Otherwise you will have to restructure moduleB to not require that functions in moduleA are run; it could require moduleA and run those functions itself.
Also try requiring moduleA from moduleB anyway, this could solve the problem.
We've been using Jasmine and RequireJS successfully together for unit testing, and are now looking to add code coverage, and I've been investigating Blanket.js for that purpose. I know that it nominally supports Jasmine and RequireJS, and I'm able to successfully use the "jasmine-requirejs" runner on GitHub, but this runner is using a slightly different approach than our model -- namely, it loads the test specs using a script tag in runner.html, whereas our approach has been to load the specs through RequireJS, like the following (which is the callback for a requirejs call in our runner):
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.TrivialReporter();
var jUnitReporter = new jasmine.JUnitXmlReporter('../JasmineTests/');
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.addReporter(jUnitReporter);
jasmineEnv.specFilter = function (spec) {
return htmlReporter.specFilter(spec);
};
var specs = [];
specs.push('spec/models/MyModel');
specs.push('spec/views/MyModelView');
$(function () {
require(specs, function () {
jasmineEnv.execute();
});
});
This approach works fine for simply doing unit testing, if I don't have blanket or jasmine-blanket as dependencies for the function above. If I add them (with require.config paths and shim), I can verify that they're successfully fetched, but all that appears to happen is that I get jasmine-blanket's overload of jasmine.getEnv().execute, which simply prints "waiting for blanket..." to the console. Nothing is triggering the tests themselves to be run anymore.
I do know that in our approach there's no way to provide the usual data-cover attributes, since RequireJS is doing the script loading rather than script tags, but I would have expected in this case that Blanket would at least calculate coverage for everything, not nothing. Is there a non-attribute-based way to specify the coverage pattern, and is there something else I need to do to trigger the actual test execution once jasmine-blanket is in the mix? Can Blanket be made to work with RequireJS loading the test specs?
I have gotten this working by requiring blanket-jasmine then setting the options
require.config({
paths: {
'jasmine': '...',
'jasmine-html': '...',
'blanket-jasmine': '...',
},
shim: {
'jasmine': {
exports: 'jasmine'
},
'jasmine-html': {
exports: 'jasmine',
deps: ['jasmine']
},
'blanket-jasmine': {
exports: 'blanket',
deps: ['jasmine']
}
}
});
require([
'blanket-jasmine',
'jasmine-html',
], function (blanket, jasmine) {
blanket.options('filter', '...'); // data-cover-only
blanket.options('branchTracking', true); // one of the data-cover-flags
require(['myspec'], function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 250;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function (spec) {
return htmlReporter.specFilter(spec);
};
jasmineEnv.addReporter(new jasmine.BlanketReporter());
jasmineEnv.currentRunner().execute();
});
});
The key lines are the addition of the BlanketReporter and the currentRunner execute. Blanket jasmine adapter overrides jasmine.execute with a no-op that just logs a line, because it needs to halt the execution until it is ready to begin after it has instrumented the code.
Typically the BlanketReport and currentRunner execute would be done by the blanket jasmine adapter but if you load blanket-jasmine itself in require, the event for starting blanket test runner will not get fired as subscribes to the window.load event (which by the point blanket-jasmine is loaded has already fired) therefore we need to add the report and execute the "currentRunner" as it would usually execute itself.
This should probably be raised as a bug, but for now this workaround works well.