Here is a wireframe to ilustrate the structure of a legacy project that shows some performance issue:
For all dialogs (From jQuery UI) open a new iframe are created and all js from Home are re-downloaded and all objects are re-instanced. Can I create a reference from jQuery from Home to all new iframes and work in each iframe isolated scope?
For example:
[Home scope]
$("#some-el").data('foo', 'bar');
console.log($("#some-el").data('foo')); // results bar
[App1 scope]
//after defined in Home first run
console.log($("#some-el").data('foo')); // results undefined
PS: Remember this is a legacy architeture and all solutions must be consider this scenario.
I've encountered this situation before. One approach is to define some javascript that gets loaded into the iframes that just reroutes any function calls to top.functionCall() instead of containing their actual definition. It becomes very simple if all of your functions are under one namespace, like so:
Parent window js:
var namespace = (function () {
// all of your functions are in here as properties of namespace
})();
iframe window js:
var namespace = top.namespace;
One issue with this is any context sensitive functions (functions that rely or operate on the window object) will most likely break.
Actually, if all of these are hosted in the same place, the browser will NOT be downloading the files multiple times. Rather, it will be caching the first result, and then pulling from cache in the second. Iframes are treated as a separate context, so you won't need to worry about variable or form conflicts.
Assuming that downloading the same file twice is your primary concern, then you should be ok there.
An alternative design would be to use AJAX instead of iframed content - but having been where you are in working with legacy apps, I realize how hard that can be to do without real JSON / REST calls available. One thing I've done is changed the views inside the iframes to be "partials," returning only the necessary HTML contents without the HTML head etc., and loading them using $.load(). This gets complex as you will need to execute bindings post-load and carefully track form ID's etc., but it can be done.
Related
I am developing an Single Page Application (SPA) from scratch. I am doing it from scratch using only HTML, CSS and vanilla JavaScript and not using any external frameworks.
My application will initially load Web page but upon navigating to some other page say page2, it will only load required data and functions about other page2 from page2.js and not reload the entire Web page.
To use the JavaScript I will append it to body. But the problem is that when I navigate same page again it will append the same JavaScript again. The more pages I visit the more scripts are attached.
I have tried removing existing script tag in favour or upcoming script and it works good, but is there a way that I don't have to append script to DOM in the first place?
So my question is, is there a way we can parse (not just plain read) or execute JavaScript file without using any physical medium (DOM)
Although I am expecting pure JavaScript, libraries would also work, just need a logical explaination
So my question is, is there a way we can parse (not just plain read) or execute JavaScript file without using any physical medium (DOM)
Yes, you can. How you do it depends on how cutting-edge the environment you're going to support is (either natively, or via tools that can emulate some things in older environments).
In a modern environment...
...you could solve this with dynamic import, which is new in ES2020 (but already supported by up-to-date browsers, and emulated by tools like Webpack and Rollup.js). With dynamic import, you'd do something like this:
async function loadPage(moduleUrl) {
const mod = await import(moduleUrl);
mod.main();
}
No matter how many times it's requested, within a realm a module is only loaded once. (Your SPA will be within a realm, so that works.) So the code above will dynamically load the module's code the first time, but just give you back a reference to the already-loaded module the second, third, etc. times. main would be a function you export from the module that tells it you've come (back) to the "page". Your modules might look like this:
// ...code here that only runs once...
// ...perhaps it loads the markup via ajax...
export function main() {
// ...this function gets called very time the user go (back) to our "page"
}
Live example on CodeSandbox.
In older environments...
...two answers for you:
You could use eval...
You can read your code from your server as text using ajax, then evaluate it with eval. You will hear that "eval is evil" and that's not a bad high-level understanding for it. :-) The arguments against it are:
It requires parsing code; some people claim firing up a code parser is "slow" (for some definition of "slow).
It parses and evaluates arbitrary code from strings.
You can see why #2 in particular could be problematic: You have to trust the string you're evaluating. So never use eval on user-supplied content, for instance, in another user's session (User A could be trying to do something malicious with code you run in User B's session).
But in your case, you want and need both of those things, and you trust the source of the string (your server), so it's fine.
But you probably don't need to
I don't think you need that, though, even in older environments. Your code already knows what JavaScript file it needs to load for "page" X, right? So just see whether that code has already been loaded and don't load it again if it is. For instance:
function loadPage(scriptUrl, markupUrl) {
// ...
if (!document.querySelector(`script[src="${scriptUrl}"]`)) {
// ...not found, add a `script` tag for it...
} else {
// ...perhaps call a well-known function to run code that should run
// when you return to the "page"
}
// ...
}
Or if you don't want to use the DOM for it, have an object or Map or Set that you use to keep track of what you've already loaded.
Go back to old-school -- web 1.0, DOM level 1.0, has your back. Something like this would do the trick:
<html><head>
<script>
if (!document.getElementById('myScriptId')) {
document.write('<script id="myScriptId" src="/path/to/myscript"></scri' + 'pt>');
}
</script>
This technique gets everybody upset, but it works great to avoid the problems associated with doing dynamic loading via DOM script tag injection. The key is that this causes the document parser to block until the script has loaded, so you don't need to worry about onload/onready events, etc, etc.
One caveat, pull this trick near the start of your document, because you're going to cause the engine to do a partial DOM reparse and mess up speculative loading.
At the risk of being down-voted for bring to vague or asking an opinion I am unsure how to handle this situation properly, or if it even matters.
I am developing a single page web application where all pages except the main page (with the menu system) are loaded dynamically via ajax. All of the javascript libraries I use (jquery, bootstrap, kendo, and others) are loaded in the main page as well as my application.js and css files.
Each page I load requires it's own javascript and has a corresponding js files named the same as the html file (for my own sanity). When the page is downloaded and set in the content element, the page js runs, which is great.
I soon realized that the page js is adding any variables it creates to the global javascript space. Ideally upon the loading of a different page those variables would go away so javascript garbage collection could eventually reclaim that memory.
To facilitate this I started to re-use the same names for common structures, like kendo viewmodels so when a new page is loaded it replaces any global variables of the same name.
In some cases where there are specialized variables I set their values to null to release the memory in the global ajax loader function.
I'm considering adding a release() function to all pages that does this and call it before the page is unloaded. Since the release function is global and each page redefines it calling it prior to swapping out the page should work.
My questions:
does it matter? should I care about a few Kb of ram that may be left lying around..
Are there better ways of handling this?
Am what I'm now doing even effective? Does javascript garbage collection clean up un-referenced ram like I'm thinking it does?
Followup after Giacomo's answer:
Giacomo your technique works in the sense that the page functions normally and the kendo bindings have access to local viewmodel variable created in the Page routine. My concern is that I don't know how it is working. I omitted the var page = Page(); assignment and simply include Page(); at the end of the script (outside of the Page() function) and there seem to be no detrimental effects.
The way I see it is the script is run when the page is set into it's parent element's html (a div), this defines then runs the Page() function, which creates all the local variables and kendo widgets/bindings on the page, then it falls out of scope. Once the Page() function has completed, it ends and there should be nothing left, however the viewmodel I created does remain because the page and it's binding all work, click events defined in Page() are executed, and so on. So, when will all that stuff go away? Will it be de-allocated when I replace the parent's html with a new page? I don't see how...of course whatever kendo is doing to keep a reference to the viewmodel seems to keep it alive, I am not confident that it gets released when the page disappears, which is my whole intent.
It could be that the page = new Page(); is essential to the de-allocation process...but I don't know how to determine that.
Possible Solution?
Can someone comment on the worth of this:
//enters here on ajax page load after inserted into DOM
var pageData = {
someData: 999,
someArray: ["one","two"],
functionOne: function(arg) {
//stuff
},
functionTwo: function(arg) {
//stuff
}
};
//do some work
pageData.functionOne(pageData.someData);
pageData.functionTwo("abc");
//called before page is removed from DOM
function releasePage() {
pageData = null;
}
//GC eventually cleans it up
you can put the js code of each page in a closure, like:
function Page() {
var var1;
var var2;
function myFunc() {
//...
}
}
and then assign it to a variable upon the "page switching"
var page = new Page();
you can keep reassigning the same variable every time
I'm shipping socket.io as part of my code for 3rd party sites to use. I don't want it to pollute the global namespace (e.g. different versions of io will collide) but rather make it work only as part of my library, So it will be namespaced and called only from
MyLibrary.io
How do I go about this?
Since you have total control over the file, then the easiest thing to do would be to just modify the file to load socket.io at a new location.
The easiest way I would say to do that would be to wrap the contents of dist/socket.io.js with
(function() {
// All Standard SocketIO code
}).call(MyLibrary);
That will make the file load io onto MyLibrary instead. SocketIO, like many libraries, uses this to decide how to load things. In a browser circumstance, this is the object window, but by wrapping a function around everything, you can control this and change it to your own value by using the method call.
Many other libraries, though not socketIO, have a helper to avoid exactly this problem. Often they have a method called noConflict(). For example, if you wanted to avoid a problem like this for jQuery, you could do:
MyLibrary.$ = MyLibrary.jQuery = jQuery.noConflict(true);
That works because when jQuery loads, it saves a reference to the previously loaded version, so that if it needs to, it can set the global objects back to how they were.
It seems that the suggested way to do this (Issue #85) is to load it as per normal then run these two lines:
MyLibrary.io = window.io; // Assign it to your desired namespace
delete window.io; // Remove it from the window namespace
The code in socket.io.js only adds the io variable to the window.
You could modify the code in socket.io.js but that might make it hard to maintain in the future since you would have to track changes you make to that file and propagate them into newer versions of socket.io.js.
At first, I thought you could do something with module.exports since it had the CommonJS format, however, after reading the code, I realized it's just the way things were organized before building the socket.io.js file. See the file directory here for what I'm referring to.
Scenario:
A web site with x number of pages is being served with a single, concatenated JavaScript file. Some of the individual JavaScript files pertain to a page, others to plugins/extensions etc.
When a page is served, the entire set of JavaScript is executed (as execution is performed when loaded). Unfortunately, only a sub-section of the JavaScript pertains directly to the page. The rest is relevant to other pages on the site, and may have potential side-effects on the current page if written poorly.
Question:
What is the best strategy to only execute JavaScript that relates directly to the page, while maintaining a single concatenated file?
Current solution that doesn't feel right:
JavaScript related to a specific page is wrapped in a "namespaced" init function for that page. Each page is rendered with an inline script calling the init function for that page. It works hunky-dory, but I would rather not have any inline scripts.
Does anyone have any clever suggestions? Should I just use an inline script and be done with it? I'm surprised this isn't more of an issue for most developers out there.
Just use an inline script. If it's one or two lines to initialize the JavaScript you need that's fine. It's actually a good design practice because then it allows re-use of your JavaScript across multiple pages.
The advantages of a single (or at least few) concatenated js files are clear (less connections in the page mean lower loading time, you can minify it all at once, ...).
We use such a solution, but: we allow different pages to get different set of concatenated files - though I'm sure there exists different patterns.
In our case we have split javascript files in a few groups by functionality; each page can specify which ones they need. The framework will then deliver the concatenated file with consistent naming and versioning, so that caching works very well on the browser level.
We use django and a home-baked solution - but that's just because we started already a few years ago, when only django-compress was available, and django compress isn't available any more. The django-pipeline successor seems good, but you can find alternatives on djangopackages/asset-managers.
On different frameworks of course you'll find some equivalent packages. Without a framework, this solution is probably unachievable ;-)
By the way, using these patterns you can also compress your js files (statically, or even dynamically if you have a good caching policy)
I don't think your solution is that bad although it is a good thing that you distrust inline scripts. But you have to find out on what page you are somehow so calling the appropriate init function on each page makes sense. You can also call the init function based on some other factors:
The page URL
The page title
A class set in the document body
A parameter appended to your script URL and parsed by the global document ready function.
I simply call a bunch of init functions when the document is ready. Each checks to see if it's needed on the page, if not, simply RETURN.
You could do something as simple as:
var locationPath = window.location.pathname;
var locationPage = locationPath.substring(locationPath.lastIndexOf('/') + 1);
switch(locationPage) {
case 'index.html':
// do stuff
break;
case 'contact.html':
// do stuff
break;
}
I'm really confused exactly why it doesn't feel right to call javascript from the page? There is a connection between the page and the javascript, and making that explicit should make your code easier to understand, debug, and more organized. I'm sure you could try and use some auto wiring convention but I don't think it really would help you solve the problem. Just call the name spaced function from your page and be done with it..
is it possible to load and use jquery in the parent iframe page & inside the iframe whithout loading jquery twice,
meaning to load jquery just once in the page and using it in the ifrmae within.
Sure, just make sure the <iframe> isn't loading the jQuery script i.e. <script src="...."> then you can access the jQuery instance in the parent like so:
var $ = window.top.$;
Be sure to use the correct context using the second parameter:
$('img', document);
If this isn't possible you can't "prevent" a script from being downloaded and executed.
You can give the child frame a reference to the jQuery object from the parent frame, but you'd have to be very careful about using it because in various places, jQuery uses the document object of the context in which it was loaded, which will (of course) be the parent's document. In some places you can override that (for instance, when searching for elements via $() you can give a context in which to search as a second parameter), but it's likely to be more trouble than it's worth.
But re-loading jQuery shouldn't be an issue. It should come from cache. Granted, there may be an extra HTTP GET with an "unmodified" response, although by using proper cache headers when serving the jQuery file, you could avoid even that. (I haven't checked the cache headers of the various CDNs that host it.)