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.
Related
When I publish my .NET app, one of my JS files doesn't minify correctly. I have several hundred lines of code in the file but end up with a near empty function after the process has completed. I have gone through it and determined it is down to a $.noop that I use, without it the process works fine. To demonstrate this I have broken it down into a simple example that shows how it affects the file.
var MyApp = {};
MyApp.EmailPopup = (function () {
function Test() {
// do lots of jquery stuff
alert('hi');
}
var thisObject = {
Show: $.noop
};
thisObject.Show = function () {
Test();
};
return thisObject;
})();
When minified the call to Test is removed as shown:
var MyApp={};MyApp.EmailPopup=function(){return{Show:$.noop}}();
However if I remove the $.noop function and add an empty function instead like so:
var MyApp = {};
MyApp.EmailPopup = (function () {
function Test() {
// do lots of jquery stuff
alert('hi');
}
var thisObject = {
Show: function () { } // this has changed
};
thisObject.Show = function () {
Test();
};
return thisObject;
})();
Then I get the desired minified version:
var MyApp={};MyApp.EmailPopup=function(){return{Show:function(){alert("hi")}}}();
In the real app, by it not including the equivalent Test function I am losing hundreds of lines of code. Can someone explain why using $.noop prevents it from working, but initialising to an empty function or null works? It is a .NET 4.8 web application, it uses jQuery 3.3.1 and I build it with Visual Studio 2019.
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);
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();
});
I have and external JS scripts file with all my objects in that runs once the document is ready something like this...
jQuery(function($) {
var Main = {
run: function () {
myFunction.setup();
}
}
var myFunction = {
setup: function() {
//Do some stuff here
}
}
Main.run();
});
I want to be able to run myFunction.setup() only if im on a certain page though otherwise I get errors if that method is looking for elements on the page that don't exist e.g a slideshow, menus etc.
At the moment I have got round this by checking if the element exists with .length and if it does then running the rest of the method but I was wondering if there was a nicer way? Maybe like if it was possible to send variables to the scripts file when it loads based on the page im on so it knows what to methods run?
Any help would be really appreciated.
Thanks
Giles
Paul Irish has a great way of doing exactly this, using ID and classes from the body tag to execute certain blocks of code:
http://paulirish.com/2009/markup-based-unobtrusive-comprehensive-dom-ready-execution/
This kind of thing might help:
Page specific
var page_config = {
setup_allowed: true
// ... more config
};
Generic
var Main,
myFunction;
(function ($, _config) {
myFunction = (function () {
var _public = {};
if (_config.setup_allowed === true) {
_public.setup = function () {
};
}
return _public;
})();
Main = (function () {
var _public = {};
if (typeof myFunction.setup !== "undefined") {
_public.run = function () {
myFunction.setup();
};
// Run it as we had Main.run() before
_public.run();
}
return _public;
})();
})(jQuery, page_config);
This way Main.run() and myFunction.setup() are only available if specified in page_config.
Here's a working example you can have a play with. This may be a bit verbose for your particular requirement but hopefully it'll help in some way :-)
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.