Only now I have considered using RequireJS and AMD modules.
So far - all the things have been managed through few global variables and self invoking functions.
Example, for how my module would looke like:
function HugeModule() {
//usage = new HugeModule();
};
HugeModule.prototype.functionX = function() {
//Lets say - around 50 functions for HugeModule prototype
};
HugeModule.SubModule = function() {
//usage = new HugeModule.SubModule();
//And here could be multiple subModules like this
};
HugeModule.SubModule.prototype.functionX = function() {
//Lets say - around 20 functions for HugeModule.SubModule prototype
};
Now I would have written it like this, I would have split it between at least 4 files:
//HugeModule.js
var HugeModule = (function() {
function HugeModule() {
//usage = new HugeModule();
};
return HugeModule;
})();
//HugeModule.somePrototypeFunctions.js
(function() {
HugeModule.prototype.functionX = function() {
//Lets say - around 50 functions for HugeModule prototype
};
})();
//HugeModule.SubModule.js
(function() {
HugeModule.SubModule = function() {
//usage = new HugeModule.SubModule();
//And here could be multiple subModules like this
};
})();
//HugeModule.SubModule.someOtherPrototypeFunctions.js
(function() {
HugeModule.SubModule.prototype.functionX = function() {
//Lets say - around 20 functions for HugeModule.SubModule prototype
};
})();
I would really like to write these modules with AMD modules and RequireJS, I have a basic idea how they should be written, but I am not sure - how would I split them between multiple modules.
I could write it like this:
define([], function() {
function HugeModule() {
//usage = new HugeModule();
};
HugeModule.prototype.functionX = function() {
//Lets say - around 50 functions for HugeModule prototype
};
return HugeModule;
});
but I would like to split it between multiple files. I would prefer not to use build tools that concatenates files.
What I would like is one requirable module - HugeModule and it would resolve all the dependencies for HugeModule.somePrototypeFunctions, and HugeModule.SubModule (and this would resolve dependencie for HugeModule.SubModule.someOtherPrototypeFunctions)
How should I resolve this?
First an important caveat: what you are trying to do does not lend itself well to how ES6 classes work. If you are ever to write ES6 classes or write in a language that has a class syntax similar to ES6 (TypeScript, for instance, has classes that are ES6 + type annotations), you'll run into having to work around the class syntax or run into transpilation problems. Think about refactoring your HugeModule into multiple smaller classes to avoid these problems. (See here for a discussion of the problem in the context of TypeScript.)
If the caveat above is not a concern, you can achieve your goal by organizing your code like the following. I've used this pattern for many years successfully.
HugeModule.js just combines the parts of the class and provide a facade for the rest of the code:
define(["./HugeModuleCore", "./HugeModuleA", "./HugeModuleB"], function (HugeModuleCore) {
return HugeModuleCore;
});
HugeModuleCore.js creates the class and creates some "core" methods on it:
define([], function () {
function HugeModule() {
};
HugeModule.prototype.someCoreFunction = function() {
};
return HugeModule;
});
HugeModuleA.js adds some category of methods to the core:
define(["./HugeModuleCore"], function (HugeModule) {
HugeModule.prototype.someFunction = function() {
};
// You don't really need to return anything here.
});
HugeModuleB.js adds some other category of methods to the core:
define(["./HugeModuleCore"], function (HugeModule) {
HugeModule.prototype.someOtherFunction = function() {
};
// You don't really need to return anything here.
});
Related
I jumped into the deep end recently and have been slowly learning to swim. I'm working on a CLI for building out a simple text game world. That code is becoming a convoluted mess and so I have tried to recreate the error I am getting in a simpler form below.
Try as I might I can't seem to understand the best way to structure all of my functions. In my project I have a parser function that breaks input up and searches for a 'verb' to invoke via a try/catch block. When a verb i.e. 'look' runs it accesses my database module and sends a query based on several parameters to return the description of a room or thing. Because this is all asynchronous virtually everything is wrapped in a promise but I am leaving that out of this example. The following is not the actual project, just a simple recreation of the way I have my objects set up.
APP:
// ***********************
const player = require('./scope_test_player');
player.look();
player.water();
Module1:
// ***********************
const apple_tree = require('./scope_test_apple_tree');
module.exports = {
look: function(){
console.log(
'The apple tree is '+apple_tree.height+'ft tall and has '
+apple_tree.apples+' apples growing on it'
);
},
water: function() {
apple_tree.grow();
}
};
Module2:
// ***********************
const player = require('./scope_test_player');
module.exports = {
height: 10,
nutrition: 0.3,
apples: [],
fertilize: function(number) {
this.nutrition+=number;
},
grow: function() {
this.height+=this.nutrition;
}
};
In the above code I get 'TypeError: apple_tree.grow is not a function' from water or undefined from look. This is the bane of my existence and I have been getting this seemingly at random in my main project which leads me to believe I dont understand scope. I know I can require the module within the function and it will work, but that is hideous and would add hundreds of lines of code by the end. How do I cleanly access the functions of objects from within other objects?
You problem is that have a cyclic dependencies in your project and that you overwrite the exports property of the module. Because of that and the way node cachges required modules, you will get the original module.exports object in scope_test_player file and not the one you have overwritten. To solve that you need to write it that way:
// ***********************
const apple_tree = require('./scope_test_apple_tree');
module.exports.look = function() {
console.log(
'The apple tree is ' + apple_tree.height + 'ft tall and has ' + apple_tree.apples + ' apples growing on it'
);
};
module.exports.water = function() {
apple_tree.grow();
};
And
// ***********************
const player = require('./scope_test_player');
module.exports.height = 10;
module.exports.nutrition = 10;
module.exports.apples = [];
module.exports.fertilize = function(number) {
this.nutrition = +number;
};
module.exports.growth = function() {
this.height = +this.nutrition;
}
But this is a really bad design in gerenal and you should find another way how to solve that. You should always avoid loops/circles in your dependency tree.
UPDATE
In node each file is wrappted into load function in this way:
function moduleLoaderFunction( module, exports /* some other paramteres that are not relavant here*/)
{
// the original code of your file
}
node.js internally does something like this for a require:
var loadedModules = {}
function require(moduleOrFile) {
var resolvedPath = getResolvedPath(moduleOrFile)
if( !loadedModules[resolvedPath] ) {
// if the file was not loaded already create and antry in the loaded modules object
loadedModules[resolvedPath] = {
exports : {}
}
// call the laoded function with the initial values
moduleLoaderFunction(loadedModules[resolvedPath], loadedModules[resolvedPath].exports)
}
return loadedModules[resolvedPath].exports
}
Because of the cyclic require, the require function will return the original cache[resolvedPath].exports, the one that was initially set before you assinged your own object to it.
Is Module1 = scope_test_player and Module2 = scope_test_apple_tree?
Maybe you have a cyclic reference here?
APP requires scope_test_player
// loop
scope_test_player requires scope_test_apple_tree
scope_test_apple_tree requires scope_test_player
// loop
As I can see scope_test_apple_tree doesn't use player.
Can you try to remove:
const player = require('./scope_test_player');
from Module2 ?
There are a few issues to address.
Remove the player require in Module 2(scope_test_apple_tree.js):
const player = require('./scope_test_player')
It doesn't do any damage keeping it there but it's just unnecessary.
Also, replace =+ with += in fertilize and grow which is what I think you are going for.
I was able to run the code natually with those fixes.
If you want to refactor, I'd probably flatten out the require files and do it in the main file controlling the player actions and explicitly name the functions with what is needed to run it (in this case...the tree).
Keeping mostly your coding conventions, my slight refactor would look something like:
index.js
const player = require('./scope_test_player');
const apple_tree = require('./scope_test_apple_tree');
player.lookAtTree(apple_tree);
player.waterTree(apple_tree);
scope_test_player.js
module.exports = {
lookAtTree: function(tree){
console.log(
'The apple tree is '+tree.height+'ft tall and has '
+tree.apples.length+' apples growing on it'
);
},
waterTree: function(tree) {
tree.grow();
console.log('The apple tree grew to', tree.height, 'in height')
}
};
scope_test_apple_tree.js
module.exports = {
height: 10,
nutrition: 0.3,
apples: [],
fertilize: function(number) {
this.nutrition += number;
},
grow: function() {
this.height += this.nutrition;
}
};
Yes, I had circular dependencies in my code because I was unaware of the danger they imposed. When I removed them from the main project sure enough it started working again. It now seems that I'm going to be forced into redesigning the project as having two modules randomly referencing each other is going to cause more problems.
I am quite new to writing javascript code using AMD. I am stuck at figuring out how to write multiple functions in a file:
define(function(){
return {
and: function(a,b){
return (a&&b);
}
};
}
);
I tried writing another function plus in the following way:
define(function(){
return {
plus: function(a,b){
return (a+b);
}
};
}
);
But when I use grunt for testing, it is not able to detect the function plus
You should place each module in it's own file. At least requireJS (are you using that?) determines the module name by it's file name (without the .js).
So a file sitting in /modules/A.js will have the module name "modules/A".
If you really want to define multiple modules in one file, you can do it in a more explicit way like this:
define("A", [], function () { return ...whatever... });
define("B", [], function () { return ...whatever... });
Edit:
for defining one module with two functions you can use different patterns. For a singleton (i.e. no "Class") I usually do something like this:
define(function () {
var myModule = {
fn1: function () { .... },
fn2: function () { .... }
};
return myModule;
});
I've wrote a small example for readability.. I'm trying to get my head around proper js app structure.
I'm new to writing larger js apps. Right now, I've got a constructor, and a whole bunch of prototype functions. I always thought you're NOT supposed to call (or return) from one function to another. But now, at the bottom of my app, I'm instantiating my constructor, then having to call a bunch of functions, as well as build in conditional statements to handle the execution, which seems totally wrong.
This is the idea I've been doing:
function TodaysFood(b, l)
{
this.breakfast = b;
this.lunch = l;
}
TodaysFood.prototype.firstMeal = function()
{
return console.log(this.breakfast);
}
TodaysFood.prototype.secondMeal = function()
{
return console.log(this.lunch);
}
var app = new TodaysFood("eggs", "sandwich");
app.firstMeal();
app.secondMeal();
I'm wondering if this function "linking" is proper?
function TodaysFood(b, l)
{
this.breakfast = b;
this.lunch = l;
}
TodaysFood.prototype.firstMeal = function()
{
return this.secondMeal(this.breakfast);
}
TodaysFood.prototype.secondMeal = function(firstMeal)
{
var twoMeals = [firstMeal, this.lunch];
return this.whatIAte(twoMeals);
}
TodaysFood.prototype.whatIAte = function(twoMeals)
{
return console.log(twoMeals);
}
var app = new TodaysFood("eggs", "sandwich");
app.firstMeal();
Stupid example, but I'm trying to understand how an app should flow. Should I be able to write my whole app in separate, but linked functions, then just kick the whole thing off by instantiating the constructor, and maybe calling one function. Or is the first example more correct -- writing independent functions, then handling the interaction between them after you've instantiate the constructor?
Thanks for any help.
You may want to make it modular, Ala Node.js or within the browser using RequireJS
Here is a slight variation of the second example you could consider, view fiddle
var TodaysFood = function (b, l) {
var self = this;
this.breakfast = b;
this.lunch = l;
this.firstMeal = function () {
console.log(this.breakfast);
return self;
};
this.secondMeal = function () {
console.log(this.lunch);
return self;
}
this.allMeals = function () {
return this.firstMeal().secondMeal();
};
}
var food = new TodaysFood('eggs', 'sandwich');
food.firstMeal().secondMeal().allMeals();
If you plan to use node.js or RequireJS then the above could be modularized by replacing the last two test lines of code with,
module.exports = TodaysFood;
If this is made modular then you would remove the constructor var TodaysFood = function(b, l) { ... and instead accept arguments for b & l within your individual methods like firstMeal & secondMeal. This would make it static and prevent collisions with the constructor values.
I have a custom matcher in some Jasmine test specs of the form:
this.addMatchers({
checkContains: function(elem){
var found = false;
$.each( this.actual, function( actualItem ){
// Check if these objects contain the same properties.
found = found || actualItem.thing == elem;
});
return found;
}
});
Of course, actualItem.thing == elem doesn't actually compare object contents- I have to use one of the more complex solutions in Object comparison in JavaScript.
I can't help but notice, though, that Jasmine already has a nice object equality checker: expect(x).toEqual(y). Is there any way to use that within a custom matcher? Is there any general way to use matchers within custom matchers?
Yes, it is slightly hacky but entirely possible.
The first thing we need to do is make the Jasmine.Env class available. Personally I have done this in my SpecRunner.html since its already setup there anyway. On the load of my SpecRunner I have the following script that runs:
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var trivialReporter = new jasmine.TrivialReporter();
jasmineEnv.addReporter(trivialReporter);
jasmineEnv.specFilter = function(spec) {
return trivialReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
};
})();
So after the execJasmine function declaration I push the jasmineEnv into the global namespace by adding this:
this.jasmineEnv = jasmineEnv;
Now, in any of my spec files I can access the jasmineEnv variable and that is what contains the matchers core code.
Looking at toEqual specifically, toEqual calls the jasmine.Env.prototype.equals_ function. This means that in your customMatcher you can do the following:
beforeEach(function(){
this.addMatchers({
isJasmineAwesome : function(expected){
return jasmineEnv.equals_(this.actual, expected);
}
});
});
Unfortunately, using this method will only give you access to the following methods:
compareObjects_
equals_
contains_
The rest of the matchers reside the jasmine.Matchers class but I have not been able to make that public yet. I hope this helps you out in someway or another
My tests fails for the following reason:
ReferenceError: Can't find variable: moving_canvas_context in file
(line 5)
I understand the reason the test is failing. It doesn't understand the variable since it is defined in a separate JavaScript file. However, it is declared globally and works in reality.
How do I write a jasmine test for this clear_canvas function?
JavaScript Canvas_Actions:
(function() {
window.Canvas_Actions = (function() {
function Canvas_Actions() {}
Canvas_Actions.prototype.clear_canvas = function() {
moving_canvas_context.clearRect(0, 0, moving_canvas.width, moving_canvas.height);
main_canvas_context.drawImage(window.background_image, 0, 0, main_canvas.width, main_canvas.height);
return window.canvas_objects = [];
};
return Canvas_Actions;
})();
}).call(this);
Jasmine Test for Canvas_Actions:
(function() {
describe('Canvas Actions', function() {
return describe('clear_canvas', function() {
return it('clears the canvases and deletes all objects', function() {
var actions;
jasmine.getFixtures().fixturesPath = "../spec/javascript/fixtures";
loadFixtures("canvas_fixture.html");
actions = new Canvas_Actions();
actions.clear_canvas();
return expect(canvas_objects).toEqual([]);
});
});
});
}).call(this);
it is declared globally and works in reality
Well, it also needs to be declared when the test runs. So you're probably missing a reference to the script where it is defined in the testing fixture html.
Also, global variables are normally not a good idea, they tend to create difficult bugs. Since you're already using jasmine as a testing framework, try to abstract the dependency on that global variable in something that you pass to your code under test. Then, use jasmine's mocking abilities to test it.
If you remove the global references from Canvas_Actions, it could look like this:
var Canvas_Actions = function(canvas) {
this.canvas = canvas;
}
Canvas_Actions.prototype.clear_canvas = function(background_image) {
var canvas = this.canvas;
canvas.getContext().clearRect(0, 0, canvas.width, canvas.height);
canvas.getContext().drawImage(background_image, 0, 0, canvas.width, canvas.height);
canvas.clearObjects();
};
You can mock the canvas argument with jasmine and test Canvas_Actions in isolation.
As can be noted, this code might unearth a Canvas class, and you might find out that clear_canvas belongs in there. Use the tests to guide your design, one step at a time.
Jordão is absolutely right, however there's an ugly option too.
Attach your global object to the window in beforeEach method. Code below probably does not work (haven't tested it), but should be good enough to understand how to work around this jasmine global object problem.
(function() {
describe('Canvas Actions', function() {
beforeEach(function () {
window.Canvas_Actions = (function() {
function Canvas_Actions() {}
Canvas_Actions.prototype.clear_canvas = function() {
moving_canvas_context.clearRect(0, 0, moving_canvas.width, moving_canvas.height);
main_canvas_context.drawImage(window.background_image, 0, 0, main_canvas.width, main_canvas.height);
return window.canvas_objects = [];
};
return Canvas_Actions;
})();
});
return describe('clear_canvas', function() {
return it('clears the canvases and deletes all objects', function() {
var actions;
jasmine.getFixtures().fixturesPath = "../spec/javascript/fixtures";
loadFixtures("canvas_fixture.html");
actions = window.Canvas_Actions;
actions.clear_canvas();
return expect(canvas_objects).toEqual([]);
});
});
});
}).call(this);
EDIT: as per comments by #John Henckel and #serv-inc apparently there might be an error (ReferenceError: window is not defined) to fix it instead of window use global like: window.Canvas_Actions change to global.Canvas_Actions
It seems like JasmineJS uses the global property. So #Jordão's answer nonwithstanding, you could replace
window.Canvas_Actions = (function() {
with
global.Canvas_Actions = (function() {