I'm using the single-page app template from https://github.com/volojs/create-template
I tried to make a simple example below.
Problem
ModuleA.js is being loaded twice, once directly from main.js and again from simulator.js which depends also on that module. This is causing two different references to an object (which I thought would only be one, like a singleton). I thought requirejs would not load the same module twice. Being (relatively) new to JavaScript I realize this may be naivete on my part. I'm trying to follow the template.
Here's a simplified version that demonstrates the problem:
www/index.html
<head>
...
<script data-main="app" src="lib/require.js"></script>
</head>
...
www/app.js
// For any third party dependencies, like jQuery, place them in the lib folder.
// Configure loading modules from the lib directory,
// except for 'app' ones, which are in a sibling
// directory.
requirejs.config({
baseUrl: 'lib',
paths: {
app: '../app'
}
});
// Start loading the main app file. Put all of
// your application logic in there.
requirejs(['app/main']);
www/app/main.js
define(function (require) {
var simulator = require('./simulator.js');
var ModuleA = require('./ModuleA.js');
ModuleA.init();
ModuleA.displayList();
ModuleA.update("joe", 99);
ModuleA.displayList();
simulator.start(); // should display the same list
});
www/app/ModuleA.js
define(function () {
var theList = {};
console.log("loading ModuleA");
return {
displayList: function () {
console.log(Object.keys(theList));
},
init : function () {
theList["fred"] = 10;
},
update : function (k, v) {
theList[k] = v;
}
}
});
www/app/simulator.js
define(["./ModuleA"], function (ModuleA) {
return {
start: function () {
ModuleA.displayList();
}
};
});
console output:
loading ModuleA
loading ModuleA
["fred"]
["fred", "joe"]
[]
The empty [] displayed on the last line is (likely) the second copy of the list due to the second loading of ModuleA.
The problem is the module references are not consistent. In main.js it requires ./ModuleA.js whereas in simulator.js it defines ./ModuleA (without the .js filetype).
Making those references identical corrects the behavior such that the module is only loaded once.
I guess I mixed the styles because of the many examples on the web. It kind of seems like a bug that it works this way, but maybe it's a feature?
If you want to share an instantiated singleton object using requireJS, you can do something like this for ModuleA:
define(function () {
console.log("loading ModuleA");
function myList(){
this.theList = {}
}
myList.prototype.displayList = function () {
console.log(Object.keys(this.theList));
}
myList.prototype.init = function () {
this.theList["fred"] = 10;
}
myList.prototype.update = function (k, v) {
this.theList[k] = v;
}
return new myList();
});
Related
I have an AngularJS web app. The app works great without any minification. But when I minified with grunt, like this:
uglify: {
options: {
report: 'min',
mangle: false
},
dist: {
files: {'app/dist/js/yanpy.min.js': [Many scripts, 'app/js/controllers/shopping-cart.js', more scripts]
I get next error:
ReferenceError: shoppingCart is not defined
at Object.$get (yanpy.min.js:9)
at Object.invoke (yanpy-libs-1.min.js:1)
at yanpy-libs-1.min.js:1
at getService (yanpy-libs-1.min.js:1)
at invoke (yanpy-libs-1.min.js:1)
at Object.instantiate (yanpy-libs-1.min.js:1)
at yanpy-libs-1.min.js:2
at yanpy-libs-1.min.js:2
at forEach (yanpy-libs-1.min.js:1)
at nodeLinkFn (yanpy-libs-1.min.js:2)
The shoppingCart script "shopping-cart.js" in included in the grunt file for minification. The file looks like:
function shoppingCart(cartName) {
this.cartName = cartName;
this.clearCart = false;
this.checkoutParameters = {};
this.items = [];
// load items from local storage when initializing
this.loadItems();
}
// load items from local storage
shoppingCart.prototype.loadItems = function () {
// Do whatever
}
What is happening? And Why is not working when I minified and it´s working without minification?
UPDATE_1: According to the mark as duplicated, please note shopping-cart is not an angular module, but just a javascript script. So, not sure if the approach provided in the original referenced post is the real answer.
UPDATE_2: According to #UncleDave comment. I copy the two parts of code there shoppingCart is referenced.
.directive('adminOfferExtras', function(Extra, ExtraService, LocationService, OfferService, DataService) {
function link(scope, element, attrs) {
var boatId = parseInt(scope.boat.id);
var extrasCart = new shoppingCart("OfferBoatExtrasCart_" + boatId);
and
.factory("DataService", function () {
// create shopping cart
var myCart = new shoppingCart("Store");
return {
cart: myCart
};
})
Please note, the only other place where I see shoppingCart referenced is in the shopping-cart.js file, that I already copied. Maybe is this call that should be marked with a var?
shoppingCart.prototype.loadItems = function () {
// Do whatever
}
Let's say I have a library module that looks like this:
module.exports = {
increment: function() {
count++;
}
}
And I'd like to use it in a dynamically generated script that looks like this:
(function() { lib.increment(); })();
by passing it in a sandbox:
var sandbox = {
count: 1
lib: require('./lib')
}
var script = new vm.Script('(function() { lib.increment() })();');
script.runInNewContext(sandbox);
The obvious problem I run into is that I on the one hand can't require "lib" because "count" is not defined in lib.js ; on the other hand if I define var count above the exports of the "lib.js" file, this new count variable will be affected instead of the one in the sandbox.
Here are the constraints that I would like to respect:
Use vm and not a eval() nor a require() on a generated file
Have "lib" defined in a external file
No modification of the automatically generated script, so no use of lib.increment.apply(context) or similar
The only solutions I've found so far is to prepend the lib functions in the generated script as a string, or to define them directly on the sandbox object, which I find to be a less desirable option.
There doesn't seem to be any way of passing a context of variables on the require call.
One way of accomplishing this is have your lib module be a function that takes in a context then returns the correct interface.
lib.js
module.exports = function(context) {
var count = context.count;
return {
increment: function() {
count++;
}
};
};
main.js
var sandbox = {
count: 1
};
sandbox.lib = require('./lib')(sandbox);
var script = new vm.Script('(function() { lib.increment() })();');
script.runInNewContext(sandbox);
For the sake of simplicity, let's say I have two modules: table.js and chart.js. I would like to bundle them using WebPack, but I end up with an error, probably due to the fact that they are dependent on each other.
table.js:
var chart = require('./chart');
module.exports = (function () {
var init = function() {
_loadTable();
chart.init();
};
var update = function() {
console.log('table updated');
};
return {
init: init,
update: update
}
})();
chart.js:
var table = require('./table');
module.exports = (function () {
var init = function() {
_drawChart();
table.update(); // will throw an error: table.update is not a function
};
return {
init: init
}
})();
What happens here?
Table is loaded first by table.init() and then loads chart module by chart.init(). Chart is drawed and tries to update the table in return.
However, at this point chart.js doesn't know what is var table = require('./table') (because at that moment var table = {}), thus table.update() will throw an error.
This didn't happen before when modules were managed "traditionally" (loaded by script tags and communicated through global window object).
I am wondering, if WebPack can still be used for parallely-dependent modules like these?
This article provides a pretty exhaustive list of what you can do to solve your problem.
http://www.bitnative.com/2015/02/03/circular-dependencies-in-requirejs/
The inline form of the requires statement is probably the quickest fix, but has numerous drawbacks.
We built a widget app with backbone and require.js. It works cool with one app instance on page. Now we have a new requirement. We need to run multiple widget instances on same page. Each of app will have its own configurations.
The following test code is not working as we expected:
for(var i=0;i<3;i++){
require([ "app" ], function(app) {
var bootstrap = {};
jQueryPB(function() {
app.testData = i;
app.startup();
});
});
}
I wonder how could I instantiate multiple apps and set different configs to them?
Project structure is similar to:
/main.js
require.config({
....
});
/*code to create multiple instances*/
require(["app"], function(app){
var instance = new app(color:"yellow");
var instance2 = new app(color:"red");
instance.render();
instance2.render();
/*want to create multiple instances here to same page*/
})
/*above code is not working, TypeError: app is not a constructor*/
/app.js
define([ "jQueryPB", "backbone", "underscore", "models/app", "views/app" ], function($jpb,
Backbone, _, appModel, appView) {
var appInfo = new appModel();
var app = new appView({
model : appInfo
});
return app;
});
/models/app.js
/views/app.js
/view/bags.js
/view/bag (it references app by var app = require("app") , so that it can access app.color)
/collection/bags
/model/bag
I use r.js to compile all js into one
node r.js -o build.js optimize=none
After main.js is fully downloaded, it would start to initialize different app instances.
=================================================updated code
cool. I tried it with similar way:
main.js
require(["app"], function(app){
var instance = new app({
testData : 1
});
instance.testData = "1";
instance.startup();
})
app.js
define([ "jQueryPB", "backbone", "underscore", "models/app", "views/app" ], function($jpb,
Backbone, _, appModel, appView) {
return function app(color) {
var appInfo = new appModel();
var app = new appView({
model : appInfo
});
console.log(">>"+color.testData);
app.testData = color.testData;
return app;
};
});
a problem is in bag.js, it needs to access the custom variable in app. I use var app = require("app"); console.log(app.testData); But the output is undefined. Is there a different way to access app instance?
In addition, if require("app"), will it cause a problem if there are multiple app instances?
The problem is, require being an asynchronous function, does not execute immediately, and when it finally does execute, the value of i will have changed. Ultimately, you will end up with all your instances sharing the same value of i, as the loop will have completed before the first require callback gets executed. See here.
You can solve this by creating a closure around each iteration of your loop. This way the original value of i is retained within the scope of the require callback.
for (var i = 0; i < 3; i++) {
(function (i) {
require(["app"], function (app) {
// etc
});
})(i);
}
Just in case it matters, I use ASP.NET 3.5 with VB.NET. I have nested MasterPages and UpdatePanels with Partial PostBacks. I include Modernizr 1.7 with YepNopeJs/IE Shim in my head section. Right before the closing body tag, I include my jQuery 1.6, jQuery UI 1.8.12, and this script.js I'm trying to build.
I'm thinking of using something like:
SITE = {
PAGES : { ... },
VARS : { ... },
HELPERS : { ... },
PLUGINS : { ... },
init : function() { ... }
};
SITE.init();
UPDATE
Ok with Levi's advice, I came up with this solution:
var SFAIC = {}; // Global namespace
SFAIC.common = { ... }; // Shared properties
SFAIC.common.fn = { ... }; // Shared functions
SFAIC.plugin = {
qtip: $.fn.qtip,
validate: $.fn.validate,
validator: $.fn.validator
};
SFAIC.init = function() { ... }; // Global initializer
$(document).ready(function() { SFAIC.init(); });
Then each page would have its own object literal like:
SFAIC.Main = {}; // Main.aspx
SFAIC.Main.someSection = { ... }; // Some Section's properties
SFAIC.Main.someSection.fn = { ... }; // Some Section's functions
SFAIC.Main.anotherSection = { ... }; // Another Section's properties
SFAIC.Main.anotherSection.fn = { ... }; // Another Section's functions
SFAIC.Main.init = function() { ... }; // Main.aspx's intializer
$(document).ready(function() { SFAIC.Main.init(); });
I recommend that you make a new object for section and a new function for each page/item.
However, the more scripts you add in this way, the harder it gets to manage the whole in an editor. Netbeans has a feature that lets you jump to parts of the object and helps manage this.
Example:
var lib = {}; // your library
//maybe you like the plural name plugins better. That's fine.
lib.plugin = {
//define plugins here
};
//maybe you like the plural name helpers better. That's fine too.
lib.helper = {
//define your helpers here
cycle: function() {
//code for the cycle plug-in
}
};
lib.account = {
//you could stick code that is general to all account pages here
};
lib.account.overview = function() {
//you could stick code that is specific to the account overview page here
//maybe you'd use the cycle plug-in to show their latest posts.
lib.plugin.cycle();
};
lib.account = {
//you could stick code that is general to all account pages here
};
lib.account.overview = function() {
//you could stick code that is specific to the account overview page here
//maybe you'd use the cycle plug-in to show their latest posts.
lib.plugin.cycle();
};
Then on the Account Overview page you'd call lib.account.overview().
For Production:
Use a package like closure, uglify or the one I mention at the end to package all your code into one file and send that.
For Development:
I would recommend for structure you use a asynchronous javascript loader like
require.js.
This means you have lot's of modules and you specifically state the dependancies.
For example you would have one main.js
// main.js
require([
"jquery.js",
"jquery.ui.js",
...
], function() {
// if the correct location is "mysite.com/foo/" then url will be "foo"
var url = window.location.pathname.match(/\/(\w+)\//)[1] || "mainpage";
require(url + ".js", function(pageObj) {
// ...
});
});
// foo.js
define({
pageStuff: ...
});
I recommend you read through the requireJS docs to understand their structuring system. It's one of the best I've found.
When it comes to optimising all javascript into one file you just use their builder. This should be part of your project deploy system.