Injecting into <head> in Vue.js - javascript

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

Related

Using Webpack, how to dynamically load an external module built with another Webpack

Let's say I manage 2 JavaScript projects both built with Webpack:
A Website called User-Website
A JavaScript module called External-Module
Note that I'm using 2 separate projets for the same reasons described by the Micro Front end architecture.
I want my User-Website to be able to dynamically load External-Module on demand (using any JavaScript module technology). My User-Website knows at build-time the URL where to reach External-Module.
I can chose whatever technology is needed on both User-Website and External-Module.
I'm looking for a solution:
That is easy to implement (maybe JSONP that Webpack already handles to dynamically load chunks?)
That doesn't add to much overhead on both User-Website and External-Module (For example JavaScript modules looks good but requires a lot of polyfilling)
My question is related to https://github.com/webpack/webpack/issues/7526
I tried to use JSONP library output on my External-Module but I don't know how to load it in User-Website.
I'm also thinking about using the SystemJS in User-Website to dynamically load External-Module:
I could also replace internal JSONP mechanism with SystemJS within Webpack (to save having JSONP mechanism in m bundles).
SystemJS looks better and more modern than RequireJS
This will require to add SystemJS (s.js only) overhead.. I'm trying to use less dependencies as possible.
I'm sorry if I did not get the question but I've recently made a project based on the article from the link You've provided.
Can't You just host both of the applications and load the part of chunk.js on the runtime ? It's how this particular article's example works. There is an example provided in the article. Some restaurant app with different microfrontends loaded dynamically on the runtime. Isn't it exactly what You need ?
Below code is the 'heart' of this particular example. Take a look.
componentDidMount() {
const { name, host } = this.props;
const scriptId = `micro-frontend-script-${name}`;
if (document.getElementById(scriptId)) {
this.renderMicroFrontend();
return;
}
fetch(`${host}/asset-manifest.json`)
.then(res => res.json())
.then(manifest => {
const script = document.createElement('script');
script.id = scriptId;
script.src = `${host}${manifest['main.js']}`;
script.onload = this.renderMicroFrontend;
document.head.appendChild(script);
});
}

Vue.js single file components WITHOUT a build process

I love vue.js because of its simplicity, which means I can hack a quick one-page SPA with modern, intuitive data-binding syntax and no complex toolchain.
I also love the idea of single-file components which means there is a single place (*.vue file) where each component stores DOM, styling and scripted functionality.
However, I want to use single-file components without wasting time on managing a build process every time I put an app together. In short, I want the benefits of component management without the overhead of a build toolchain, which means letting the browser do the heavy lifting for bootstrapping each *.vue file via XMLHttpRequest and DOM rendering. Making sure that we replace module.exports and import calls with corresponding Vue.component() functionality.
I'd love to know if anyone has come across a client-side (only) solution for using *.vue files on the browser. Surely this has been done already?
I'm absolutely certain this doesn't exist yet, because while it might seem relatively easy, certain functionalities would make it quite difficult to implement. For example:
You don't necessarily import just other .vue components, you can import random external dependencies. Which means that the browser now needs to download and parse npm modules, handle their dependencies, etc.
Different sections of your .vue component (template, logic and style) can be written in languages other than HTML, JS and CSS. Which means the browser now also needs to download a compiler/transpiler for Jade, CoffeeScript, LESS or whatever else you're using and run your code through it. Mind, there's no guarantee that such a transpiler written in JavaScript actually exists, because a node module used in a regular build process could be just a wrapper for some external library which can't be run in a browser.
Styling in a .vue component can be scoped, which means that you now need to parse the template of a component to insert randomly generated IDs as element attributes AND parse the styling of the same component to insert those same IDs in your CSS selectors so that your styling ends up being scoped.
And those are just the most obvious ones off the top of my head. Sure, you could severely limit yourself and not use any of those features, but then it's not really a .vue component anymore, is it?
If you really want to avoid a build process at all costs and are willing to accept the limitations of not using any of the features above, why not just use a single JS file:
$(body).append(`<style>
// styling goes here
</style>`);
var myTemplate = `
// template goes here
`;
Vue.component('my-component', {
template: myTemplate
// component logic goes here
})
You have to load them in the correct order, but there you have it, a poor man's single file component.
Another way is use: http-vue-loader
Load .vue files directly from your html/js. No node.js environment, no build step.
https://cdn.jsdelivr.net/npm/http-vue-loader#1.4.1/src/httpVueLoader.min.js
Same to in unpkg cdn
https://unpkg.com/http-vue-loader
Here a example
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/http-vue-loader"></script>
<script>
new Vue({
el: '#app',
components: {
'header': httpVueLoader('/components/header.vue'),
'nav-bar': httpVueLoader('/components/navbar.vue'),
'aside': httpVueLoader('/components/aside.vue'),
'content': httpVueLoader('/components/content.vue'),
'footer': httpVueLoader('/components/footer.vue')
}
});
</script>
Or you can load your components from external like
'MyHelloWorldComponent': httpVueLoader('https://my-cdn-or.github.io/path/HelloWorld.vue'),
See the example at : https://codepen.io/mikechen2017/pen/wEdbBN/
It's 2020 and Evan You wrote https://github.com/vuejs/vite just last week.
I'd love to know if anyone has come across a client-side (only) solution...
Vite has a server, but it feels like the old days of Web when we just had Notepad. I had run the demo in less than 5 minutes, it's that easy.
it covers or aims to cover the finer details that #mzgajner mentions
For now, I would say that it's only gotcha is that you are in Vue 3 beta realm right away if you use it. No Vue 2.x.
However, I want to use single-file components without wasting time on managing a build process every time I put an app together. In short, I want the benefits of component management without the overhead of a build toolchain
I share the sentiment and decided to solve this problem with vue-blocks. Just a single script tag to get going, no build tools required, completely client-side.
It can load vue files (with some limitations though) from the server jsfiddle example:
<template src="path/to/vue-file.vue"></template>
Vue Blocks allows you to write multiple vue components in the html document, like so:
<template component="sample-component">
<div>
<h1>Sample component</h1>
</div>
<style>
</style>
<script>
export default {
data() {
return {}
},
mounted() {},
methods: {
xx() {
}
}
}
</script>
</template>
A working demo in jsfiddle: https://jsfiddle.net/o48L0y9j/

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.

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).

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.

Categories