Exploring the dynamic loading of components within routes

Just starting out with Vue and experimenting with vue-router, I'm trying my hand at dynamically loading components without relying on additional libraries like webpack. So far, I've set up an index page and a router. Upon initial page load, I notice that the subpage.js file is not loaded. However, upon clicking the <router-link>, I can see that the subpage.js file is indeed loaded, though the URL remains unchanged and the component doesn't appear.

This is what my setup looks like:

index.html

<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
    <div id="app">
      <h1>Hello App!</h1>
      <router-link to="/subpage">To subpage</router-link>
      <router-view></router-view>
    </div>
    <script src="main.js"></script>
</body>
</html>

main.js

const router = new VueRouter({
  routes: [
    { path: '/subpage', component: () => import('./subpage.js') }
  ]
})

const app = new Vue({
    router
}).$mount('#app');

subpage.js

export default {
    name: 'SubPage',
    template: '<div>SubPage path: {{msg}}</div>'
    data: function() {
        return {
            msg: this.$route.path
        }
    }
};

In essence, my question revolves around how to effectively load a component dynamically.

Answer №1

Is there a way to dynamically load a component in Vue?

Here's an example of how you can achieve that:

App.vue

<template>
  <div id="app">
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
    <hr/>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'app',
  components: {}
};
</script>

main.js

import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';

Vue.use(VueRouter);
Vue.config.productionTip = false;

const Home = () => import('./components/Home.vue');
const About = () => import('./components/About.vue');

const router = new VueRouter({
  mode: 'history',
  routes:[
    {path:'/', component: Home},
    {path:'/about',component: About}
  ]
})
new Vue({
  router,
  render: h => h(App)
}).$mount('#app');

Home.vue

<template>
  <div>
    <h2>Home</h2>
  </div>
</template>

<script>
export default {
  name: 'Home'
};
</script>

About.vue

<template>
  <div>
    <h2>About</h2>
  </div>
</template>

<script>
export default {
  name: 'About'
};
</script>

By using this method, the component Home will be loaded automatically.

To see a live demo, visit: https://codesandbox.io/s/48qw3x8mvx

Answer №2

I support the idea of having a codebase that is as lean as possible, so I've created a simple example code below (you can also find it at https://codesandbox.io/embed/64j8pypr4k).

While I am not an expert in Vue, I have explored three options:

  • dynamic imports,
  • requireJS,
  • including old-school JS generated <script src />.

It seems like the last option is the easiest and requires the least effort :D However, it may not be the best practice and could become obsolete soon with dynamic import support.

Please note: This example is optimized for modern browsers (with native Promises, Fetch, Arrow functions...). To test it properly, use the latest version of Chrome or Firefox :) Supporting older browsers might require polyfills and refactoring, but it will significantly increase the codebase...

So, the focus here is on dynamically loading components when needed (rather than including them upfront):


index.html

<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Vue lazyload test</title>
  <style>
html,body{
  margin:5px;
  padding:0;
  font-family: sans-serif;
}

nav a{
  display:block;
  margin: 5px 0;
}

nav, main{
  border:1px solid;
  padding: 10px;
  margin-top:5px;
}

    .output {
        font-weight: bold;
    }

  </style>
</head>

<body>
    <div id="app">
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/simple">Simple component</router-link>
      <router-link to="/complex">Not sooo simple component</router-link>
    </nav>
      <main>
          <router-view></router-view>
    </main>
    </div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/2.0.1/vue-router.min.js"></script>
<script>
    function loadComponent(componentName, path) {
      return new Promise(function(resolve, reject) {
        var script = document.createElement('script');

        script.src = path;
        script.async = true;

        script.onload = function() {
          var component = Vue.component(componentName);

          if (component) {
            resolve(component);
          } else {
            reject();
          }
        };
        script.onerror = reject;

        document.body.appendChild(script);
      });
    }

    var router = new VueRouter({
      mode: 'history',
      routes: [
        {
          path: '/',
          component: {
            template: '<div>Home page</div>'
          },
        },
        {
          path: '/simple',
          component: function(resolve, reject) {
            loadComponent('simple', 'simple.js').then(resolve, reject);
          }
        },
         { path: '/complex', component: function(resolve, reject) { loadComponent('complex', 'complex.js').then(resolve, reject); }
        }
      ]
    });

    var app = new Vue({
      el: '#app',
      router: router,
    });
</script>

</body>

</html>

simple.js:

Vue.component("simple", {
  template: "<div>Simple template page loaded from external file</div>"
});

complex.js:

Vue.component("complex", {
  template:
    "<div class='complex-content'>Complex template page loaded from external file<br /><br />SubPage path: <i>{{path}}</i><hr /><b>Externally loaded data with some delay:</b><br /> <span class='output' v-html='msg'></span></div>",
  data: function() {
    return {
      path: this.$route.path,
      msg: '<p style="color: yellow;">Please wait...</p>'
    };
  },
  methods: {
    fetchData() {
      var that = this;
      setTimeout(() => {
        /* a bit delay to simulate latency :D */
        fetch("https://jsonplaceholder.typicode.com/todos/1")
          .then(response => response.json())
          .then(json => {
            console.log(json);
            that.msg =
              '<p style="color: green;">' + JSON.stringify(json) + "</p>";
          })
          .catch(error => {
            console.log(error);
            that.msg =
              '<p style="color: red;">Error fetching: ' + error + "</p>";
          });
      }, 2000);
    }
  },
  created() {
    this.fetchData();
  }
});

The function loadComponent() handles the "magic" of loading components in this scenario.

While this approach works, it may not be the most optimal solution due to potential issues such as:

  • injecting tags using JavaScript could pose security risks in the future,
  • synchronously loading files can block the thread and affect performance later on,
  • lack of testing for caching, which could lead to problems in production,
  • missing out on the benefits of Vue components, like scoped CSS, HTML, and automated bundling with tools like Webpack,
  • omitting Babel compilation/transpilation, affecting cross-browser compatibility,
  • loss of features like Hot Module Replacement and state persistence,
  • potentially overlooking other issues that experienced developers might catch.

Hopefully, this information proves helpful nevertheless! :D

Answer №3

Curious about the functionality of "new" dynamic imports in modern web development, I decided to conduct some experiments to test their effectiveness. Dynamic imports do simplify the process of asynchronous loading, as demonstrated in the example code below (without Webpack or Babel, just using pure Chrome-friendly JS).

While I maintain my previous answer for potential reference on loading components dynamically in routes, it's worth noting that loading scripts in this manner is more widely supported across browsers compared to dynamic imports (https://caniuse.com/#feat=es6-module-dynamic-import).

In the end, I discovered that the solution was quite close to what you had attempted - a simple syntax error involving a missing comma when exporting an imported JS module.

The following example code worked for me, although Codesandbox's (es)lint flagged the syntax issue. Upon local testing, it functioned successfully in Chrome, although Firefox encountered a syntax error (SyntaxError: the import keyword may only appear in a module):


index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" /> 
  <title>Page Title</title>
</head>

<body>
    <div id="app">
      <h1>Hello App!</h1>
      <router-link to="/temp">To temp</router-link>
      <router-link to="/module">To module</router-link>
      <router-view></router-view>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    <script src="main.js"></script>
</body>
</html>

main.js:

'use strict';

const LazyRouteComponent = {
    template: '<div>Route:{{msg}}</div>',
    data: function() {
        return {
            msg: this.$route.path
        }
    }
}


const router = new VueRouter({
  routes: [
    {
        path: '/temp',
        component: {
            template: '<div>Hello temp: {{msg}}</div>',
            data: function() {
                return {
                    msg: this.$route.path
                }
            }
        }
    },
    { path: '/module', name: 'module', component:  () => import('./module.js')},
    { path: '*', component: LazyRouteComponent }
  ]
})

const app = new Vue({
    router
}).$mount('#app');

and the key difference, module.js:

export default {
    name: 'module',
    template: '<div>Test Module loaded ASYNC this.$route.path:{{msg}}</div>',
    data: function () {
       return {
          msg: this.$route.path
       }
    },
    mounted: function () {
      this.$nextTick(function () {
        console.log("entire view has been rendered after module loaded Async");
      })
    }
}

This code closely resembles yours but with all the necessary commas included;

subpage.js

export default {
    name: 'SubPage',
    template: '<div>SubPage path: {{msg}}</div>',
    data: function() {
        return {
            msg: this.$route.path
        }
    }
};

Your code functions properly (confirmed through copy-pasting). The missing comma after

template: '<div>SubPage path: {{msg}}</div>'
was the only issue.

However, note that this method seems to work only in the following browsers:

  • Chrome >= v63
  • Chrome for Android >= v69
  • Safari >= v11.1
  • IOS Safari >= v11.2

(https://caniuse.com/#feat=es6-module-dynamic-import)...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Verifying the format of an object received from an HTTP service using a TypeScript interface

Ensuring that the structure of the http JSON response aligns with a typescript interface/type is crucial for our javascript integration tests against the backend. Take, for example, our CurrentUser interface: export interface CurrentUser { id: number; ...

Unable to scroll to the top of the page with JavaScript

I tried the code below but it didn't quite do the trick. Can someone assist me with refreshing the page to the top every time it loads? window.addEventListener('load', function(){ window.scrollTo(0,0) }) window.onload = (event) => { ...

The entire component is not displayed in Vue

Just starting out with Vue. I'm looking to create a component that encompasses the entire page except for the header and footer Vue.component('main-block', {template: ` <section id="first-block">...</section> <secti ...

The functionality for selecting text in multiple dropdown menus using the .on method is currently limited to the first dropdown

Having trouble selecting an li element from a Bootstrap dropdown and displaying it in the dropdown box? I'm facing an issue where only the text from the first dropdown changes and the event for the second dropdown doesn't work. <div class="dr ...

ways to animate the closing of a dropdown menu

I'm currently working on a navbar dropdown animation code that works on hover. However, I've encountered an issue when trying to use it on a phone - I can open the dropdown, but I can't seem to close it. Does anyone have suggestions on how ...

Some inquiries regarding the fundamentals of JavaScript - the significance of the dollar sign ($) and the error message "is not a function"

Teaching myself JavaScript without actually doing any explicit research on it (crazy, right?) has led to a few mysteries I can't quite crack. One of these mysteries involves the elusive dollar sign. From what I gather, it's supposed to be a con ...

In Vue3, a certain variable can be set either through props or using ref

// Here is some pseudo code for handling modelValue and image props = { modelValue? } image = ref() onPageLoad = function { if modelValue is null then: image.value = await api.get() } <button @onClick> // Displaying image based on condition ...

Instructions for transforming DOM information into a JSON format

I have multiple inputs within my document, as shown in the code snippet below. My goal is to create a string or JSON object using the input names and values. var arr= []; $('ul li input').each(function(){ let name = $(this).attr(' ...

Implement the callback-console.log feature from the epic-games-api into an Express.js application

Looking to integrate Epic Games output into an Express.js GET request but don't have any JavaScript experience, so go easy on me! XD const EpicGamesAPI = require('epicgames-status'); const express = require('express') const app = ...

Converting data into a multidimensional array in AngularJS: A comprehensive guide

Struggling to convert a JSON array into a multidimensional array, looking for some guidance. I attempted using _.groupBy in underscore.js, but it's not proving successful with the nested array. If there is any way to convert from the given data [{ ...

How can you access PHP $_GET values using JavaScript?

Recently, I encountered an issue with my app that is running on localhost/index.php. In my JavaScript code, I am using the History API as follows: history.replaceState({}, null, "?uid=" + uid); to update the URL of the page. Here, the variable uid holds ...

What are the appropriate times to utilize functional components in Vue.js?

After learning about functional components in Vue.js 2 (Documentation), I discovered that they offer significant performance improvements: Functional components, being just functions, are much more efficient to render. It appears that when emitting even ...

The clone() function in jQuery causes the properties of the original element to become distorted

Seeking help with a curious issue I'm encountering. After selecting a radio button and cloning it, the original radio button becomes unchecked while the cloned one behaves as expected. Any insights into why this might be happening would be greatly app ...

Trigger an Angular2 component function from an HTML element by simply clicking a button

I'm just starting out with TypeScript and Angular2 and encountering an issue when trying to call a component function by clicking on an HTML button. When I use the **onclick="locateHotelOnMap()"** attribute on the HTML button element, I receive this ...

Retrieve information from various tables in a SQLite database using Node.js

Is there a method to retrieve all data from multiple tables in a database? Currently, I have managed to fetch all data from a single table: router.get('/', function (req, res, next) { db.serialize(function () { db.all('SELECT id, name ...

"What's the best way to update the src attribute of an iFrame when a user clicks

I'm new to coding in PHP or JavaScript, so please bear with me if my question isn't quite right. I'm looking for a way to dynamically pass and embed a URL from a link into the src attribute of an iframe. Essentially, I want users to click o ...

Using Javascript or jQuery, focus on a div containing a paragraph element with a particular text

I've been struggling for hours trying to figure out how to select a div that contains a specific p element. HTML <div class="NavBar_Row_Button2"><p>DONATE</p></div> <div class="NavBar_Row_Button2"><p>CONTACT</p ...

My loop encountered an 'Undefined' error while attempting to retrieve an object from a JSON file

Hey everyone, I could use some help with a problem I'm having in my for loop. The issue is that I keep getting an "undefined" word in the output of my loop. I am trying to retrieve objects from a JSON file and the loop itself seems to be working corre ...

Can Vue Storefront Nuxt be seamlessly integrated with Algolia Search Routing in SSR for optimal performance?

Successfully integrated Algolia search with the VSF/Next branch, mastered the basics. Next up: tackling routing. While Vanilla Nuxt works smoothly with the integration, the number of workarounds is gradually increasing. To Reproduce: clone && yarn && ya ...

Tips for creating a Vue component that triggers the select dropdown to open when the entire div or wrapper is clicked

I have a custom-designed select dropdown with unique symbols for the select arrow. To achieve this look, I've hidden the default select arrow/triangle and placed our symbol on top as an image file. <div class="select-wrapper"> < ...