simulate fbasyncInit behaviour on a webpack bundled library - javascript

Im using webpack to build a library targeted for browsers. And i Would really like to replicate fbasyncinit behavior without manually modyfing the bundled library.
Does webpack provide any way to call a function after loading the library itself ?
Or is there another alternative bundler that allows this?
For those unnaware, window.fbasyncinit is a function facebook sdk calls when finish loading, so you write the function to initialize facebook sdk stuff.

Depending on your setup you might want to make your library external and lazy load it into the target application. In a nutshell this means that your library exists alongside the application but won't actually be loaded on initialization. It could then be loaded by an event or after some setTimeout.
Webpack watches for special comments to tell it what code needs to be separated into its own bundle:
button.onclick = event => {
const loadLodash = await import(/* webpackChunkName: "lodash" */ "lodash");
console.log('lodash loaded');
};
This kind of comment,/* webpackChunkName: "lodash" */, is one such type. Note that until whatever event or timeout occurs, your library will not be loaded.
To be honest I haven't done this for libraries in particular so I'm not sure of the difficulties this might impose here.
Update
Actually, there might be a simpler answer to this. I forgot that lazy loading is actually just dynamically creating a script tag with a src to the library you want to load (using a bundled lodash library for example).
var script = document.createElement('script');
script.src = 'http://localhost:8080/vendors.[hash].js';
document.head.appendChild(script);
If you need to control when your library is loaded you could just trigger it this way after some other code (something that just exists to load the library) detects a page load.
Update 2
After rereading this, it may also be just as simple as triggering your fbasyncinit simulated behavior after some event when you know the library is loaded into the app (assuming your library doesn't depend on any network calls to initialize). All of the other ideas apply as well, but may be more complicated than you need. Before trying the external/lazy-load solutions I'd suggest looking at something simple like this within your library:
window.addEventListener('load', event => {
// your custom fbasyncinit behavior here
});
This is of course after everything in the page has been loaded, which may be too late depending on your needs.

Related

Injecting into <head> in Vue.js

I have a few EXTERNAL scripts that need to be loaded on various pages, such as Google Places Autocomplete, Facebook APIs, etc.
Obviously it does not make sense to load them on every route, however the documentation does not address this rather common scenario.
Furthermore, the Vue instance mounts onto a tag within the body, since
the mounted element will be replaced with Vue-generated DOM in all cases. It is therefore not recommended to mount the root instance
to < html > or < body >.
How are real world applications currently dealing with this situation?
I recommend using https://www.npmjs.com/package/vue-head, it is exactly designed to inject the data you want from your component into the document's head.
Perfect for SEO title and meta tags.
To be used like so:
export default {
data: () => ({
title: 'My Title'
}),
head: {
// creates a title tag in header.
title () {
return {
inner: this.title
}
},
meta: [
// creates a meta description tag in header.
{ name: 'description', content: 'My description' }
]
}
}
This isn't addressed in documentation because it's not Vue's job. Vue is meant for creating, among other things, single page applications (SPA). In a single page application you typically load all your vendor scripts (Google, Facebook, etc.) and your own scripts and styles the first time the site loads.
A lot of real world applications just bundle all their vendor scripts into a single file (example: vendor.js) using Webpack, Gulp, Grunt or some other bundling tool. The rationale is that you can pack all those scripts into a single file, minify them, serve them with gzip compression and then it's only a single request.
Smarter bundlers like Webpack and Browserify can walk the dependency tree if you're using require() or import to import your modules. This can be allow you to split your dependencies into several logical chunks so components and libraries only load load with their dependencies if they themselves are loaded.
We had this issue as well. We load JavaScripts by other means. We created a library that does this for us (quick and dirty, observing browser events and adding the JavaScript tag). This library also, implements a mediator pattern (https://addyosmani.com/largescalejavascript/#mediatorpattern) fires an event of "page:load" (custom for us) at the very end once all libraries have been loaded.
Our VueJS components are executed only when that event fires. This also allowed us to put Vue components in the header tag instead of body, as the browser will load it, but not execute the function until the event is fired.
var loadVueComponents=function()
{
var myComponents=new Vue({....});
};
Mediator.subscribe("page:load",loadVueComponents);
Before I get downvotes for not using Webpack or any of those other tools, this was an old application with a lot of JavaScripts (some cannot be minified or even concatenated/bundle with other files) and we needed to add some new components (now using Vue) and trying to disrupt as little as possible existing pages (mediator pattern was already implemented and loading dynamic libraries based on page attributes)
I think you are talking about some external JS which are not part of node-modules and want to retrieve from external source (http://your-external-script) then you can go for dynamic loading of JS script tag. Put this code somewhere like you first landing screen of SPA in before transition event.
var script = document.createElement('script');
script.src = "htpps://your-external-script.js";
document.head.appendChild(script); //or something of the likes
This will make your external file available in global scope and then you can use it anywhere.
Note: this scenario is where you dont haev node-moduels for library or you dont want to put as load modules

Intercepting HTML imports

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.

Avoid loading DOM relient modules while testing

I'm using the intern for testing javascript in a project. The javascript we are trying to test does not require the DOM, and should be able to be run within a node process without trouble. However, the modules that I'm loading for the test also load jquery (as an example), which hits the dom on load.
//file.js
require([..., 'jquery'], function(..., $) {
// non-jquery code to test
})
//intern test
require(['intern!object', 'file'], function(registerSuite, file) {
registerSuite({
...
Loading jquery in the module causes intern to break immediately when run in a node process, even though I want to test something completely different. Is there a correct way to get around this? I tried just pointing the jquery module to an empty file, but this doesn't work for all cases.
Thank you.
If any code has a hard dependency on the complete, pre-built copy of jQuery, then it must run in a DOM environment, as jQuery is a DOM library. If the code itself doesn’t require a DOM, then it shouldn’t have jQuery as a dependency, at which point it will load fine in Node.js.
If you need a non-DOM utility library, try Dojo or Lo-Dash or something else that isn’t a DOM library like jQuery.
If you have a module that may use the DOM, Dojo comes with the dojo/has module that can be used as an AMD loader plugin to conditionally load DOM dependencies only when the module is loaded in a browser.
Finally, note that any AMD module (like file.js in your example) should contain a define call, not a require call.

Make an external Script available to all members of qooxdoo Object

So I have been making a web app in the qooxdoo framework that utilised the d3 library. At the moment, every function which needs to use the d3 library works like this:
myFunction : function() {
var req = new qx.bom.request.Script();
req.onload = this.myActualFunction(); //calls function when script loads
req.open("GET","http://d3js.org/d3.v3.js" );
req.send();
}
It seems verbose to have to call the script loader for lots of different functions*. We could, and eventually probably will, switch to using d3 from a local directory. Nevertheless, it seems like there are lots of times when you would like to use a script loader to make a script available to, say, every member function of an object. Is there someway that I can achieve that? If I passed the script loader around like a variable, does that mean that every function which has it in scope gets access to the library?
The Manual did not appear very helpful on this topic.
*I presume that qooxdoo arranges to cache the script - it seems to be pretty good at those type of optimisations, though I have no specific knowledge of how the script loaders are treated in the compiled version.
The best approach of handling an external library is to create a Qooxdoo wrapper which will be a first-class citizen in the dependency management process. Qooxdoo internally makes use of this approach, e.g. Sizzle, Mustache, etc. Luckily for you there's already a contribution for D3. Though you can make your own wrapper without a hassle.
You can also, use add-script to "link" the library to the document but I see this way mostly disadvantageous.

Mastering external scripts loading order in Meteor (Google Maps)

I tried unsuccessfully to add a google map(externally loaded script) to a meteor app, and I noticed there were two kinds of problems:
If I do the simple thing and add the main API script to my <head></head>, then it gets rendered last.
When this happens, I am obliged to insert any scripts that depend on the API again in my template's <head> - after the main API script. (otherwise scripts complain they don't see the API blabla..)
Then the time for the actually function call comes - and now putting it inside <head> after the rest won't work. You need to use Template.MyTemplate.rendered.
Basically my question is:
What's the cleanest way to handle these kinds of things?
Is there some other variable/method I can use to make sure my Google main API file is called very first in my HTML?
I just released a package on atmosphere (https://atmosphere.meteor.com) that might help a bit. It's called session-extras, and it defines a couple functions that I've used to help with integrating external scripts. Code here: https://github.com/belisarius222/meteor-session-extras
The basic idea is to load a script asynchronously, and then in the callback when the script has finished loading, set a Session variable. I use the functions in the session-extras package to try to make this process a bit smoother. I have a few functions that have 3 or 4 different dependencies (scripts and subscriptions), so it was starting to get hairy...
I suppose I should add that you can then conditionally render templates based on whether all the dependencies are there. So if you have a facebook button, for example, with helpers that check the Session variables, you can give it a "disabled" css class and show "loading facebook..." until all the necessary scripts have loaded.
edit 03/14/2013
There is also an entirely different approach that is applicable in many cases: create your own package. This is currently possible with Meteorite (instructions), and the functionality should soon be available in Meteor itself. Some examples of this approach are:
jquery-rate-it: https://github.com/dandv/meteor-jquery-rateit
meteor-mixpanel: https://github.com/belisarius222/meteor-mixpanel
If you put a js file in a package, it loads before your app code, which is often a good way to include libraries. Another advantage of making a package is that packages can declare dependencies on each other, so if the script in question is, for example, a jQuery plugin, you can specify in the package's package.js file that the package depends on jQuery, and that will ensure the correct load order.
Sometimes it gets a little more interesting (in the Chinese curse sense), since many external services, including mixpanel and filepicker.io, have a 2-part loading process: 1) a JS snippet to be included at the end of the body, and 2) a bigger script loaded from a CDN asynchronously by that snippet. The js snippet generally (but not always!) makes some methods available for use before the bigger script loads, so that you can call its functions without having to set up more logic to determine its load status. Mixpanel does that, although it's important to remember that some of the JS snippets from external services expect you to set the API key at the end of the snippet, guaranteed to be before the bigger script loads; in some cases if the script loads before the API key is set, the library won't function correctly. See the meteor-mixpanel package for an example of an attempt at a workaround.
It's possible to simply download the bigger js file yourself from the CDN and stick it in your application; however, there are good reasons not to do this:
1) the hosted code might change, and unless you check it religiously, your code could get out of date and start to use an old version of the API
2) these libraries have usually been optimized to load the snippet quickly in a way that doesn't increase your page load time dramatically. If you include the bigger JS file in your application, then your server has to serve it, not a CDN, and it will serve it on initial page load.
It sounds like you're loading your Javascript files by linking it with HTML in your template. There's a more Meteor way of doing this:
From the Meteor Docs:
Meteor gathers all JavaScript files in your tree with the exception of
the server and public subdirectories for the client. It minifies this
bundle and serves it to each new client. You're free to use a single
JavaScript file for your entire application, or create a nested tree
of separate files, or anything in between.
So with that in mind, rather than link the gmaps.js into head, just download the un-minified version of gmaps and drop it in you application's tree.
Also from the Meteor Docs:
It is best to write your application in such a way that it is
insensitive to the order in which files are loaded, for example by
using Meteor.startup, or by moving load order sensitive code into
Smart Packages, which can explicitly control both the load order of
their contents and their load order with respect to other packages.
However sometimes load order dependencies in your application are
unavoidable. The JavaScript and CSS files in an application are loaded
according to these rules:
Files in the lib directory at the root of your application are loaded
first.
[emphasis added]
And if the sequence is still an issue, drop the js file into client/lib and it will load before all the Javascript you've written.
I've used meteor-external-file-loader and a bit of asynchronous looping to load some scripts, which will load javascript (or stylesheets) in the order you specify.
Make sure to have meteorite and add the package above >> mrt add external-file-loader
Here's the function I wrote to make use of this package:
var loadFiles = function(files, callback) {
if (!callback) callback = function() {};
(function step(files, timeout, callback) {
if (!files.length) return callback();
var loader;
var file = files.shift();
var extension = file.split(".").pop();
if (extension === "js")
loader = Meteor.Loader.loadJs(file, function() {}, timeout);
else if (extension === "css") {
Meteor.Loader.loadCss(file);
loader = $.Deferred().resolve();
}
else {
return step(files, timeout, callback);
}
loader.done(function() {
console.log("Loaded: " + file);
step(files, timeout, callback);
}).fail(function() {
console.error("Failed to load: " + file);
step(files, timeout, callback);
});
})(files, 5000, callback);
}
Then to use this, add to one of your created methods for a template like so:
Template.yourpage.created = function() {
var files = [
"//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js",
"javascripts/bootstrap.min.js"
];
loadFiles(files, function() {
console.log("Scripts loaded!");
});
}
Quick edit: Found it's just a good idea to place the functionality from the .created method in the /lib folder in Meteor.startup();
Meteor.startup(function() {
if (Meteor.isClient) {
// Load files here.
}
});
Caveat: Lots of javascript files = really long load time.... Not sure how that will be affected by the normal meteor javascript files, and the load order there. I would just make sure that there are no conflicts until users take action on the website (or that if there is, they are loaded first).

Categories