preface
One problem with single-page apps is the slow rendering of the first screen. This is because the server sends a lot of JavaScript to the client when the page first loads, which must be downloaded and parsed before anything can be displayed on the screen. You can imagine that as your application grows in size, the impact of this problem on the user experience becomes more pronounced.
Now fortunately, when building Vue applications using the Vue CLI (using Webpack), there are steps you can take to counteract this. In this article, I’ll show you how to load parts of a page after the initial rendering of an application using asynchronous components and webPack’s code-splitting capabilities. This will minimize initial load times and provide a better user experience for your application.
Learning about asynchronous components
Before we start creating asynchronous components, let’s look at how we normally load components. To do this, we’ll use a very simple message component as an example:
<! -- Message.vue --> <template> <h1>New message! </h1> </template>Copy the code
Now that we have created our Message component, let’s load it into our file and display it. We can import the component and add it to the component options so that we can use it in the template:
<! -- App.vue --> <template> <div> <message></message> </div> </template> <script> import Message from"./Message";
export default {
components: {
Message
}
};
</script>
Copy the code
But what happens now? The Message component is loaded as soon as the application loads, so it is included in the initial loading process.
This may not seem like a big deal for a simple application, but consider a complex scenario like an e-commerce site. Imagine that a user adds an item to a shopping cart and then wants to check out, so clicking the check out button generates a box with all the details of the selected item. Using the method above, this checkout box will be included in the initial package, but we only need to use this component when the user clicks the checkout button. Users can even navigate the site without clicking the checkout button, which means it doesn’t make sense to waste resources while loading a component that might not be used.
To improve application efficiency, we can combine lazy loading and code splitting techniques.
Webpack provides code splitting capabilities that allow you to break your code into bundles that can then be loaded on demand or later loaded in parallel. It can only load specific snippets of code when needed or used.
Dynamic Imports
Vue uses Dynamic Imports to solve this situation. This feature introduces a new function-like import form that returns promises containing (Vue) components. Since import is a function that accepts strings, we can do powerful things like load modules using expressions. Dynamic imports have been available in Chrome since version 61. For more information on these, visit the Google Developers website.
Code splitting is handled by bundlers such as Webpack, Rollup, or Parcel, which parse the dynamic import syntax and create separate files for each dynamically imported module. We’ll see this later in the network TAB of the console. But first, let’s look at the differences between static and dynamic imports:
// static import
import Message from "./Message";
// dynamic import
import("./Message").then(Message => {
// Message module is available here...
});
Copy the code
Now, let’s apply this knowledge to our Message component, which we’ll get as follows: app.vue
<! -- App.vue --> <template> <div> <message></message> </div> </template> <script> import Message from"./Message";
export default {
components: {
Message: () => import("./Message")}}; </script>Copy the code
As you can see, the function import() resolves the Promise that returns the component, which means that we have successfully loaded the component asynchronously. If you look at the DevTools Network TAB, you’ll notice a file called 0.js that contains asynchronous components.
Load asynchronous components based on conditions
Now that we’ve mastered the asynchronous components, let’s load them only when we really need them. In the previous section of this article, I explained the use case for a checkout box that loads only when the user clicks the checkout button. Let’s build it out.
Project Settings
If you don’t have vue/ CLI installed, you should install it first:
npm i -g @vue/cli
Copy the code
Next, use the CLI to create a new project and select Default when prompted:
vue create my-store
Copy the code
Go to the project directory and install the Ant-Design-vue library we will use for styles:
cd my-store
npm i ant-design-vue
Copy the code
Next, import the Ant design library: SRC /main.js
import 'ant-design-vue/dist/antd.css'
Copy the code
Finally, we create two new components in SRC /comonents: checkout. vue and Items. Vue:
touch src/components/{Checkout.vue,Items.vue}
Copy the code
Write a store view layer
Open SRC/app. vue and replace the file with the following code:
<template>
<div id="app">
<h1>{{ msg }}</h1>
<items></items>
</div>
</template>
<script>
import items from "./components/Items"
export default {
components: {
items
},
name: 'app'.data () {
return {
msg: 'My Fancy T-Shirt Store'
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
Copy the code
There’s nothing fancy here. All we do is display a message and render a
component.
Next, open SRC/Components/kitems.vue and add the following code:
<template>
<div>
<div style="padding: 20px;">
<Row :gutter="16">
<Col :span="24" style="padding:5px">
<Icon type="shopping-cart" style="margin-right:5px"/>{{shoppingList.length}} item(s)
<Button @click="show = true" id="checkout">Checkout</Button>
</Col>
</Row>
</div>
<div v-if="show">
<Row :gutter="16" style="margin:0 400px 50px 400px">
<checkout v-bind:shoppingList="shoppingList"></checkout>
</Row>
</div>
<div style="background-color: #ececec; padding: 20px;">
<Row :gutter="16">
<Col :span="6" v-for="(item, key) in items" v-bind:key="key" style="padding:5px">
<Card v-bind:title="item.msg" v-bind:key="key">
<Button type="primary" @click="addItem(key)">Buy ${{item.price}}</Button>
</Card>
</Col>
</Row>
</div>
</div>
</template>
<script>
import { Card, Col, Row, Button, Icon } from 'ant-design-vue';
export default {
methods: {
addItem (key) {
if(! this.shoppingList.includes(key)) { this.shoppingList.push(key); } } }, components: { Card, Col, Row, Button, Icon, checkout: () => import('./Checkout')
},
data: () => ({
items: [
{ msg: 'First Product', price: 9.99}, {MSG:'Second Product', price: 19.99}, {MSG:'Third Product', price: 15.00}, {MSG:'Fancy Shirt', price: 137.00}, {MSG:'More Fancy', price: 109.99}, {MSG:'Extreme', price: 3.00}, {MSG:'Super Shirt', price: 109.99}, {MSG:'Epic Shirt', price: 3.00},], shoppingList: [], show:false
})
}
</script>
<style>
#checkout {
background-color:#e55242;
color:white;
margin-left: 10px;
}
</style>
Copy the code
In this file, we display a shopping cart icon with the number of items. The item is extracted from the Items array. If you click the Buy button for an item, the addItem method is called, which pushes the related item into the shoppingList array. Thus increasing the total number of shopping carts.
We also added a Checkout button to the page:
<Button @click="show = true" id="checkout">Checkout</Button>
Copy the code
When the user clicks this button, we set the parameter show to true. True is very important for conditional loading of our asynchronous components.
In the next few lines, you can find the v-if declaration, which only displays the
Here we load the Checkout component asynchronously in the Components option. Here v-bind passes the parameters to the component. As you can see, creating conditional asynchronous components is easy:
<div v-if="show">
<checkout v-bind:shoppingList="shoppingList"></checkout>
</div>
Copy the code
Let’s take a quick Checkout component to add the following code in the SRC/components/Checkout. Vue:
<template>
<Card title="Checkout Items" key="checkout">
<p v-for="(k, i) in this.shoppingList" :key="i">
Item: {{items[Number(k)].msg}} for ${{items[Number(k)].price}}
</p>
</Card>
</template>
<script>
import { Card } from 'ant-design-vue';
export default {
props: ['shoppingList'],
components: {
Card
},
data: () => ({
items: [
{ msg: 'First Product', price: 9.99}, {MSG:'Second Product', price: 19.99}, {MSG:'Third Product', price: 15.00}, {MSG:'Fancy Shirt', price: 137.00}, {MSG:'More Fancy', price: 109.99}, {MSG:'Extreme', price: 3.00}, {MSG:'Super Shirt', price: 109.99}, {MSG:'Epic Shirt', price: 3.00},]})} </script>Copy the code
Here, we will receive a shoppingList and print it to the screen.
You can run the application using the NPM run serve command. Then navigate to http:// localhost: 8080. If all goes according to plan, you should see something like the image below.
Try opening the Network TAB and clicking the Checkout button to see that the Checkout component will load asynchronously in the network.
You can also view the code for this demo on GitHub.
Add load and load error components for asynchronous components
Sometimes asynchronous components load too long or when loading. Displaying loading animations or error messages can be useful, but support this again slows down the application. Asynchronous components should be small and fast to load. Here’s an example:
const Message = () => ({
component: import("./Message"),
loading: LoadingAnimation,
error: ErrorComponent
});
Copy the code
conclusion
Creating and implementing asynchronous components is simple enough that it should be part of standard development routines. From a user experience perspective, it is important to keep the initial load time as low as possible to keep the user’s attention. Hopefully, this tutorial has helped you build asynchronous load components.