DEV Community

Magnus Manske
Magnus Manske

Posted on

vue.js modules in the browser, the cheap way

I really enjoy vue.js, and prefer it to React et al.
One advantage of vue is the ability to use it in the browser directly, without having to pre-process it on the server, with webpack or similar. But how to separate your vue components in a purely browser-based setup?

  • You can put your JavaScript into .js files, but how to separate/include <template>s?
  • How to keep JavaScript, templates, and (potentially) CSS together?

Ideally, I want to put JS, template code, CSS, and potential dependencies (e.g. non-vue JS libraries) for one component in a single file. However:

  • <script> doesn't work if your file does include non-JavaScript code
  • <link> didn't work for me
  • RequireJS didn't work either (didn't dwell on it though)
  • Up-and-coming ES6 include/require doesn't seem to be there/wide-spread enough yet

I found a low-tech solution that works for me, and may be of use to some of you, so here goes:

  • I put all my vue components into a single directory, one file each, e.g. component-one.html
  • Each file consists of <template>, <style>, <script> etc. tags; just plain HTML, essentially
  • To use components in a new app, I simply add this:
vue_components.loadComponents (
    ['component-one','component-two','...'] , 
    function(){
        // Initialise router and mount app
        router = new VueRouter({routes}) ;
        app = new Vue ( { router } ) .$mount('#app') ;
    } 
) ;

The vue_components object is defined in a separate JS file (my version requires JQuery, for the load method):

var vue_components = {
    template_container_base_id : 'vue_component_templates' ,
    components_base_url : 'https://my.favourite.server/resources/vue/' ,
    loadComponents : function ( components , callback ) {
        var me = this ;
        var cnt = 0 ;
        $.each ( components , function ( dummy , component ) {
            cnt++ ;
            me.loadComponent ( component , function(){
                if ( --cnt == 0 ) callback() ;
            } ) ;
        } ) ;
    } ,
    loadComponent ( component , callback ) {
        var me = this ;
        var id = me.template_container_base_id + '-' + component ;
        if ( $('#'+id).length > 0 ) return callback() ;
        $('body').append($("<div id='"+id+"' style='display:none'>"));
        var url = me.components_base_url+component+'.html' ;
        $('#'+id).load(url,function(){ callback() }) ;
    }
}

This adds each component in their own <div> at the end of <body>, templates, css, code, and all. Only when all components are loaded, the router and app are initialised. AFAICT, this works in every browser that works with JQuery. I just started using this system, and will undoubtedly improve on it, but I welcome any constructive feedback!

Top comments (3)

Collapse
 
ycmjason profile image
YCM Jason

Interesting idea! Thank you for sharing this.

Just a few JS advice. (You might lose a little bit of browser support, but since you are using object method shorthand, it shouldn't support IE and safari already according to MDN.)

  1. Don't use var anymore. Use const and let instead.
  2. Don't use me = this, use arrow function to have the context inherited.
  3. Use promise instead of callback. (Eliminate the use of counter by using Promise.all)
  4. Avoid jquery
  5. Use either
{
    f: function(){
        ...
    }
}

Or

{
    f(){
        ...
    }
}

Not both. Although they are essentially the same thing, it is important to have consistency.

Collapse
 
magnusmanske profile image
Magnus Manske • Edited

Thanks! This is what it looks like in proper ES6 (some parts omitted):

let vue_components = {
    loadComponents : function ( components ) {
        return Promise.all ( components.map ( component => this.loadComponent(component) ) ) ;
    } ,
    loadComponent ( component ) {
        let id = this.getComponentID ( component ) ;
        if ( $('#'+id).length > 0 ) return Promise.resolve(42) ; // Already loaded/loading
        $('body').append($("
").attr({id:id}).css({display:'none'})); return fetch ( this.getComponentURL(component) ) .then ( (response) => response.text() ) .then ( (html) => $('#'+id).html(html) ) } }

I tried to get rid of jQuery, but document.getElementById("content").innerHTML = html doesn't seem to work for some reason, even though it does set the HTML. But I need jQuery for the larger project anyway, so that's OK...

Collapse
 
rhymes profile image
rhymes

You could probably replace jQuery with fetch but you lose IE (Edge works).

If IE is a requirement and since you don't need to do anything fancy you could try using a library like nanoajax so you can replace jQuery with that.