Intercepting HTML imports - javascript

I can't find anything around the web. I'm using Polymer 1.6, and I'm trying to do Lazy Loading of the elements. So far I've succeeded in Lazy Loading them and speed has increased considerably.
I'm doing the App Shell architecture, in which I bundle (through minification and vulcanization) all the scripts that are needed for navbar and drawer work.
But, as soon as I do that, there are many HTML imports that are part of the App Shell, that will be called because they differ in name.
I could remove the HTML imports from my elements, but that would be error prone. Note: I know that HTML imports are only executed once, but since they are part of a bundle the browser does not know how to prevent its load.
So what I want to do is to intercept HTML imports, check if the element is part of the App Shell, and prevent its load if it already exists.
Something like this:
var appShellComponents : [
'polymer'
'my-navbar',
'paper-button',
'app-drawer'
document.addEventListener('HTMLImportEvent', function(event){
//Untested code below
var href = event.srcTarget.href;
var component = href.substr(href.lastIndexOf('/').replace('.html','');
if(appShellComponents.indexOf(component) > -1){
//Element has been loaded, reject the import.
return;
}
});
I also need a way to do it with other browsers such as Firefox. Apparently Polymer uses a polyfill that invokes AJAX instead.

The best way is to process the files at the server-side, with a program like gulp or grunt for example.
You could replace the attribute rel='import' of the <link> elements with your own custom name, for example rel='lazy-import', and then process them with your own module loader in the browser.
If you want to handle the links at runtime on the client side, you could wait for the onload event on link (or the HTMLImportsLoaded on document), but I guess these events are fired too late for what you want to achieve (in fact it depends on how the different Web Components are designed).
However with the polyfill (for Firefox and Internet Explorer), it could be possible, because you can patch the code which realizes the XMLHttpRequest calls. Unfortunately it won't work for Chrome and Opera because the <link> are parsed and processed natively and the <script> in the imported files are executed immediately when they are downloaded.
A workaround could be to move the Web Components in a folder so that they won't be found with the initial relative href URL (or use a <base> element with a wrong url). Then you'll just have to insert good <link> when needed.

Related

How to use javascript without appending it to DOM

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.

How to load error trackers from script tag?

I'm working on small .js which is going to be embedded on multiple websites, it will be loaded in a classic way - via script tag: <script src="myscript.js"></script> in sites body tag. I cannot add any more scripts to those sites.
I would like to track errors with error tracker such as Sentry, Rollup or HoneyBadger. However, all of them require being loaded with another script tag, most preferred before everything else.
Note: Those services need to load before everything else to catch errors property.
As I cannot add another script tag in the site's code, I need to execute their code inside my script, but before my actual script code.
I tried taking the content of HoneyBadger javascript library and putting it directly inside my file - it worked, however, I feel like it's terrible practice, as their code is written with modern browsers in mind, and mine supports older ones.
Is there any good way in my situation to load their .js externally?
I don't think that would work because of the way honeybadger.js v0.5 parses the script tag to get those attributes--it looks for the script tag in the dom when it's loaded.
Also, we've moved away from using the data- attributes in honeybadger.js v1.0, which was just released. In that version, you must use Honeybadger.configure to set your API key. Take a look at the new docs here:
https://docs.honeybadger.io/lib/javascript/integration/browser.html
I'd recommend going with v1.0, and using Honeybadger.configure for the configuration.

A way of checking if javascript files are loading in order?

I understand that if I have a js file (x.js) that has some function and another file (y.js) that uses the function from x.js then when loading the files, I must load x.js then y.js
In bigger projects, 1) is there a way - i.e. editor tool, plugin, console tool, website - that checks if JS files are loaded in the right order? or 2) is there a standardized method of checking the order of JS file load?
I picked up* a huge project last week at work and sometimes the main page loads correctly with no console errors, other times I get a number of console errors.
*The project had many parts in the past, but right now they cut it down to one part. However, the JS files have way too many dependencies and the person who worked before me did not bother cleaning the files, marking unused functions, or even commenting.
I believe you can mark the scripts that have dependencies as defer so they won't load until other scripts load first. From the html5 spec:
If the element has a src attribute, and the element has a defer
attribute, and the element has been flagged as "parser-inserted", and
the element does not have an async attribute The element must be added
to the end of the list of scripts that will execute when the document
has finished parsing associated with the Document of the parser that
created the element.

How to load page specific JS files with RequireJS?

I have a multiple page website using RequireJS, which loads a boot strap file (boot.js), which then requires app.js.
app.js handles all the logic, and all other module initialization happens through app.initModule() (which is just a require() call wrapper)
I also have a app.loadPageJS() to load page specific JS files (based on window.location.pathname, for example, www.domain.com/path/to/file.html would auto-load /_assets/js/pages/path/to/file.js)
This feature can be turned on/off, and overridden by adding a class of "no-auto-load" or "auto-load" to the body, respectively.
Now, my approach isn't robust enough. For one, url rewriting would break the mechanism, and for two, if loadPageJS is turned off, unless I have access to the body tag, I can't include a page specific JS file (in the case of sites using templating systems, adding a class to the body tag isn't always an option).
What are other ways to include page specific code? I'd rather avoid the following:
adding page specific code to a global.js file and doing if checks and only running certain code sets
using a pageName variable (which would essentially be similar to the above)
Thanks in advance.
If you have different modules on the page sectioned by unique ID's (a newsletter module wrapped within a div with an ID of 'newsletter', etc), you could test for existence of the module element in the DOM and conditionally load in the JS file necessary to run that module. So rather than being page-specific, it is module specific.

GWT bookmarket or GWT as an external library

I simply want to load a GWT(Google Web Toolkit) app by adding a script tag to the DOM, however because the GWT linker uses document.write() I'm unable to find any good way of doing so. I've found some hacks for doing so on various blog posts but they all seem to fail with the latest version of GWT. Any reasonably non-invasive approach for doing this come to mind?
Clarification:
Normal way to start up a GWT app, in your host html page:
<script type="text/javascript" language="javascript" src="myapp.nocache.js"></script>
This, of course, starts up as soon as the page loads. I want to do it at a later time:
function startapp() {
var head = document.getElementsByTagName('head');
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('src', 'myapp.nocache.js');
head[0].appendChild(s);
}
Here's what seems to work so far:
Add this to the top of your App.gwt.xml:
<!-- Cross site linker -->
<inherits name="com.google.gwt.core.Core" />
<add-linker name="xs" />
After compiling your app with the above setting, modify (or copy) the generated app.nocache.js as follows:
1) Comment the last $doc.write... statement
2) Copy this portion from the $doc.write statement you just commented out and eval it. Example:
eval('window.__gwtStatsEvent && window.__gwtStatsEvent({' + 'moduleName:"app", sessionId:window.__gwtStatsSessionId, subSystem:"startup",' + 'evtGroup: "loadExternalRefs", millis:(new Date()).getTime(),' + 'type: "end"});' + 'window.__gwtStatsEvent && window.__gwtStatsEvent({' + 'moduleName:"app", sessionId:window.__gwtStatsSessionId, subSystem:"startup",' + 'evtGroup: "moduleStartup", millis:(new Date()).getTime(),' + 'type: "moduleRequested"});');
3) Add this line right after.
document.body.appendChild(document.createElement('script')).src=base + strongName + ".cache.js";
So you're basically replacing the $doc.write with those two lines.
Now, your bookmarklet will look something like:
My App
I'm assuming you are already using the cross-domain linker and this does not resolve your problem with document.write. If not, it might be worth a look (sorry, not enough experience with it to say.)
One approach that I am fairly sure could be made to work is this:
Your bookmarklet adds a script tag to the page (as now)
This script is not GWT compiler output. It is a plain-old javascript that adds an IFrame to the page, and the src of that IFrame is pointed at an HTML page on your server that loads your GWT module.
Presumably the goal is for your GWT module to get things out of the page it was loaded into. Of course, it can't do this directly in this case because the IFrame comes from a different domain than the parent page.
In order to make this work you would have to use window.postMessage and window.addEventListener to communicate between your GWT module in the IFrame and your javascript stub in the parent (using JSNI on the GWT side.)
If you have to support older browsers, postMessage won't work - but you might be able to get away with hash manipulation - but this is probably where I'd draw a line on practicality.
Whenever a browser loads a javascript file, its also execute every line of it inorder to build the symbol tables etc.
In your case, the app loads in the browser and after the dom is loaded, your GWT module js gets loaded. At this point, the browser will try to execute every line of the GWT module javascript, possibly causing your earlier loaded DOM to go for a toss.
What exactly is your use case? If your requirement is conditionally loading the GWT module then your could try something like this:
Include this in your head:
<script src="gwtmoduleloader.js"></script>
Here, gwtmoduleloader.js is infact a servlet that will hold logic to figure out if the gwt module is to be loaded.
If the GWT module is to be loaded, the sevlet can print a
document.write('<script src="myapp.nocache.js"></script>')
or else return silently.
When browser evaluates the contents of gwtmoduleloader.js, it may find a document.write for another script (in your case the gwt module), which it will load and evaluate. This is thus a conditional load and can be achieved before the body begins loading.
Is this what you were looking for?

Categories