What’s New in Vue.js 3?

Since the release of Vue.js 2, the Vue.js community has grown rapidly and has received great reviews from users. Along with Angular and React, it is one of the most popular JavaScript frameworks. Vue.js 3 is about to be released and has caused quite a hype in the world of web development in the last few months.

Improvements

The new version of Vue.js is designed for speed. Although Vue 2 was already relatively fast, Vue 3 has up to 100% better update performance and up to 200% faster server-side renderings. Component initialization is also now more efficient, even with compiler-informed fast paths to execution. The virtual DOM has also been completely rewritten and outperforms its old version in terms of speed.

There are also improvements in terms of size. Vue 2 was already pretty darn small with a weight of about 20 kB gzipped. With a constant baseline size of <10 kB gzipped, Vue 3 weighs only half as much. How? Mostly by eliminating unused libraries (tree shaking). So if you don’t use an item, it’s not included.

Vue 3 will support TypeScript. Besides, the packages will be decoupled, which will make everything more modular and easier to maintain.

Building native applications with Vue isn’t all that difficult, but with Vue 3 the runtime core is platform-independent, which makes it even easier to use this technology with any type of platform.

As you can see, it’s worth taking a look at what new features will be at Vue 3.

Composition API

So far, we used the Options API when creating new components. This API forced us to separate the component’s code by options, which meant we had all reactive data in one place (Data), all calculated properties in one place (Computed) and all methods in one place (Methods).

Although it is manageable and readable for smaller components, it becomes painful when the component becomes more complex and has to do with multiple functionalities. Usually, the logic related to a particular functionality contains some reactive data, calculated properties, a method, or a few thereof; sometimes it also involves the use of component lifecycle hooks. As a result, when working on a single logical issue, you have to keep jumping back and forth between different options in the code.

The other problem you might have encountered while working with Vue is how to extract common logic that can be reused by multiple components. Vue already has few options for doing this, but each has its drawbacks (e.g. mixins and scoped slots).

Vue 3’s solution to this is a setup () method that allows us to use the composition syntax. Every piece of logic outside of this method is defined as a compositional function.

Code example from a color-picking game using the composition API:

<script>
import Vue from "vue";
import ColorChoiceButton from "@/components/ColorChoiceButton";
import GameControlHeader from "@/components/GameControlHeader";
import { reactive } from "vue";
import { getRandomColor } from "@/utils/getRandomColor";
 
export default {
  components: {
    GameControlHeader,
    ColorChoiceButton
  },
  setup() {
    const score = reactive({
      lost: 0,
      won: 0
    });
 
    const colors = reactive({
      picked: "",
      right: "",
      choices: ["", "", ""]
    });
 
    function pickColor(color = "") {
      colors.picked = color;
      if (colors.picked === colors.right) {
        //round won
        score.won++;
      } else {
        //round lost
        score.lost++;
      }
    }
    function setFreshColors() {
      colors.choices[0] = getRandomColor();
      colors.choices[1] = getRandomColor();
      colors.choices[2] = getRandomColor();
 
      // we should detect the rare case that we get the same color twice and generate colors again
 
      const randomChoice = Math.round(Math.random() * 2);
      colors.right = colors.choices[randomChoice];
      colors.picked = "";
    }
    function resetGame() {
      score.won = 0;
      score.lost = 0;
      setFreshColors();
    } //new colors, score = 0
 
    resetGame();
 
    return { pickColor, resetGame, setFreshColors, score, colors };
  }
};
</script>

In short, it’s just a function that returns properties and functions to the template. We declare all reactive properties, computed properties, watchers and lifecycle hooks here and then return them so that they can be used in the template.

The Composition API is a great way to make code more readable and maintainable. It will help make larger Vue projects more modular and reusable; a very promising sign of the developer-friendly changes the Vue team is making. It seems like they discovered many pain points from the development teams and tried to come up with workable solutions without making extremely drastic changes.

Multiple root elements

In Vue 2, the template tag can only hold one root element. Even if we only have two

– Tags, we’d have to put them in one

– Tag to make it work. 

Therefore we also had to change the CSS code in the parent component to look as expected.

In Vue 3, this limitation has been removed. A root element is no longer required.

We can use any number of tags right inside the section <template></template>:

<template>
  <p> Count: {{ count }} </p>
  <button @click="increment"> Increment </button>
  <button @click="decrement"> Decrement</button>
</template>

Suspense

Suspense is a component needed during lazy loading, its main purpose being to encase lazy components. Several lazy components can be wrapped with the suspense component. Version 3 of Vue.js introduces Suspense to wait for nested asynchronous dependencies in a nested tree, and it works well with asynchronous components. Instead of our component, fallback content is displayed until the desired condition is met. The condition is usually an asynchronous process that takes place within our component setup function.

Example:

<Suspense>
  <template >
    <suspended-component />
  </template>
  <template #fallback>
    Loading...
  </template>
</Suspense>

Portals

The component moves all children to the first element in the DOM tree described by the target. The target works like a conventional query selector, i.e. an element can be selected via the ID (#target), the class (.target) or the tag name (e.g. main).

With teleports, elements placed in front of the content such as modals, (cookie) banners, pop-ups, etc. can be called from anywhere in the app, but they always render at the same place in the DOM tree.

App.vue

<Component>
  <teleport to="#target">
    <div>...</div>
  </teleport>
</Component>

index.html

<body>
  ...
  <div id="app"></div>
  <div id="portal-target"></div>
</body>

Multiple V-Models

If you use Vue, you already know that v-models are used for bidirectional data binding of Vue components. In Vue 2, you get a v-model for a component, but there’s great news in this new version!

You can now have as many v-model declarations and bindings per component as you want and also name the individual v-models.

Something like this is now possible:

<InviteeForm
   v-model:name="inviteeName"
   v-model:email="inviteeEmail"
/>

Global mounting/configuration API change

We can find another major change in the way we instantiate and configure our application. We are currently using the global Vue object to deploy any configuration and create new Vue instances. Any change made to the Vue object will affect every Vue instance and component.

Now let’s look at how it will work in Vue 3:

import { createApp } from 'vue'
import App from './App.vue'
 
const app = createApp(App)
 
app.config.ignoredElements = [/^app-/]
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
 
app.mount('#app')

Conclusion

Aside from the Composition API, which is the biggest new API in Vue 3, we can also find a lot of smaller improvements. We can see that Vue is moving towards a better experience for developers and simpler, more intuitive APIs. It’s also great to see that the Vue team has decided to incorporate many ideas that are currently only available through third-party libraries into the core of the framework.

Vue Barcamp Berlin 2019

After months of waiting, the time had finally come. From October 25 to October 27, the PHMU team made their way to the federal capital, Berlin, with luggage and anticipation. The aim of the trip was the Vue Barcamp 2019.

Gruppe von PHMU

After the drive and arrival in Berlin, the discovery was the first thing on our to-do list. Right after the first step out of the train, we were struck by the size of Berlin. Day or night, there are crowds, music, lights. That’s when you first notice that Dresden is still a small, manageable city. After we let off steam, we went to the hotel to prepare for an interesting day.

Day 1

After we arrived at the venue on time, despite getting on the wrong track, the bar camp started immediately.

At a Barcamp, participants plan the program themselves by presenting a topic or suggesting a problem that they want to discuss. Then, according to the number of interested parties, the topic is added to the program or not.

Vue Barcamp Präsentation

This enabled enough topics to be found on the first day to fill a whole day and beyond.

Programm Barcamp Vue
Program schedule for Saturday

From workshops for Vue.js beginners, frameworks announcements to discussions about the upcoming update for Vue.js 3.0, everything was there.

Diskussion Barcamp Vue
Discussion about what’s new in Vue.js 3.0
Philipp von PHMU auf Barcamp Vue
Phillipp told the community about his work with Storybook

Day 2

On the second day, the program was again full of interesting content, stimulated by discussions and topics on the previous day.

Barcamp Vue

My experience

As you can see, the Barcamp draws its charm from the exchange of information among Vue.js developers. As in life, it follows the principle of giving and take. By sharing our experiences from our projects with the Vue.js community, we receive lots of good suggestions for future projects from the community.

Ramona from Shopware was kind and introduced us to the cypress.io test framework on the first day and we were immediately impressed. This has led us to work with the framework in our future projects.

All in all, the Vue Barcamp was an exciting event. In the beginning, as a Vue.js beginner, I was very afraid of getting lost in the eddy current of expert words. But as it turned out, many participants had just started with Vue.js themselves or who wanted to learn it. So the fear was wrong. Since the topics are broad, there is always a topic for every skill level. I can warmly recommend such a Barcamp to everyone. It doesn’t always have to be Vue.js 😉

How to use Tailwind CSS in the Vue.js project

You have successfully started a Vue project (we have written how it works here) and now you want to use TailwindCSS as a utility framework for development? Then in the following article, we will show you how you can do this relatively easily.

First, you have to install the necessary packages:

npm install tailwindcss autoprefixer
-- ODER --
yarn add tailwindcss autoprefixer

Then you can create the tailwindcss.config.js file in your project with the following command:

npx tailwind init

Once that’s done, you can create a new / css folder under assets and create the tailwind.css file there. Here you add the following imports:

@tailwind base;
@tailwind components;
@tailwind utilities;

Now we have to tell the Vue app that the CSS styles should be used. To do this, we import our new tailwind.css into the App.vue.

@tailwind base;
@tailwind components;
@tailwind utilities;

In the last step, we adapt our postcss.config.js. So we state that we want to use both plugins.

module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
  ]
}

Once that’s done, we can check that everything works. It’s best to add a Tailwind CSS class like bg-red-500 to a div in App.vue and see if you can see the change after the live reload. Does everything fit? Perfect, then you have successfully added TailwindCSS to your project.

Optimize bundle size

Tailwind increases your CSS immensely. So if you run npm run build you should see the change. The CSS is now over 679 KiB. However, we can reduce that relatively easily.

File                                      Size             Gzipped

dist/js/chunk-vendors.9080b304.js         119.90 KiB       41.68 KiB
dist/js/app.d8b6098b.js                   7.19 KiB         2.58 KiB
dist/service-worker.js                    0.96 KiB         0.54 KiB
dist/js/about.edf670de.js                 0.44 KiB         0.31 KiB
dist/css/app.6504396d.css                 679.39 KiB       85.10 KiB

To remove unused Tailwind CSS classes from your bundle, we use PurgeCSS in conjunction with PostCSS. To do this, first add the necessary package to the project:

npm install purgecss  @fullhuman/postcss-purgecss

Once the installation is complete, we have to make the following change in the PostCSS Config:

const purgecss = require(“@fullhuman/postcss-purgecss”);

module.exports = {
    plugins: [
      require(‘tailwindcss’),
      require(‘autoprefixer’),

    // Only add purgecss in production
    process.env.NODE_ENV === 'production'
      ? purgecss({
          content: ['./src/**/*.html', './src/**/*.vue'],
        })
      : '',
  ],
}

If we now start npm run build again, we should already see drastic improvements in the CSS file.

File                                      Size             Gzipped

dist/js/chunk-vendors.9080b304.js         119.90 KiB       41.68 KiB
dist/js/app.d8b6098b.js                   7.19 KiB         2.58 KiB
dist/service-worker.js                    0.96 KiB         0.55 KiB
dist/js/about.edf670de.js                 0.44 KiB         0.31 KiB
dist/css/app.69044861.css                 2.14 KiB         0.81 KiB

2.14 KiB so you can start!

Link: TailwindCSS (https://tailwindcss.com/)

Setting up a VueJS project using Vue CLI

There are different ways to start a new VueJS project. The easiest and most convenient way is the setup using the Vue CLI. To be able to use the CLI, the necessary NPM modules must first be installed globally. The following command can be executed in the terminal for this purpose:

npm install -g @vue/cli

#oder 

yarn global add @vue/cli 

If the installation is successful, a new command is then available in the terminal. With vue -v, for example, you can check which version of the Vue CLI is now installed.

The project setup via the CLI

To start a project setup, you can start the setup process via the terminal with the help of vue create + the desired project name.

vue create vue-setup

After the command, a small wizard starts that guides you through the setup. First, you are asked whether you want to carry out the configuration manually or use the standard setup, which only includes Babel and ESLint. We first select manually with the arrow keys and then the space bar to see which options are offered.

vue-cli

In manual mode, all options and packages that can be used now appear. For example, I can add the Vue Router to my project relatively quickly and easily. But the setup for unit and end-to-end testing can also be selected.

After the desired selection has been made, the installation of the project and the necessary packages can be started.

A visual project setup using the CLI UI

If you are not one of the terminal enthusiasts, a project setup can also be carried out using a modern interface. To do this, start the UI in the terminal with:

vue ui

This opens the Vue UI interface on port 8000. Project Create starts the process that is already familiar with the CLI when initializing a new project.

First, you can choose the storage location and specify the project name. You can also choose between npm or yarn as the standard package manager. However, a specification is not a must. Once the setting has been made, the wizard process is also started here.

A manual setup can also be carried out via the UI if, for example, the Vue router or Vuex should be added during the setup.

vue-ui

After the successful setup, you are ready to go and can further develop your Vue project.

How-to: Upload directly from the front end of a Vue app into an AWS bucket with SignedURLs

If you want to provide an upload for users in your Vue app, this is usually done via your backend and the associated data handling. With the help of an AWS bucket and the SignedURLs, this is relatively easy to achieve, without placing a lot of load on your personal backend.

I will report on my experiences and learnings from implementing this solution. Maybe it will help one or the other reader to get there a little faster.

The blog post shows the setup with which the upload to AWS can be set up and you can do without a backend server completely. The learning curve at AWS is a bit steep, but once the basic setup is in place, the upload can be implemented quickly.

This is what you need to start

Setup of your AWS console

Step 1: Create an account

The first step is to create an AWS account. AWS always asks for the credit card details for a new account, although we later adjust the setup so that there are no costs for the time being. After successful registration, we have access to the keys belonging to the account. The keys shown are the root keys, which we will not use now because an IAM user will be created for the upload. If you want to use these keys later, you can simply have new keys generated for you at any time under “My Security Credentials”.

AWS Konsole

Note: You should never share your keys publicly because the “pay as you go” pricing model could quickly get expensive for you. You are on the safest path if you save the keys as variables in an .env file in your project and ignore them in your Git repository.

Step 2: create a bucket

Once the account has been created, the bucket creation can continue. A bucket can be created relatively easily by giving it a name and deactivating all approval for the time being. This is important because only then can the bucket policy be set to public to make the images accessible to everyone.

AWS Konsole
Pic: Create a public bucket (also turn off last approval)

Your bucket policy and CORS settings should look like this:

 {
    "Version": "2012-10-17",
    "Id": "public policy example",
    "Statement": [
        {
            "Sid": "Allow get requests",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::YOUR_BUCKET/*"
        }
    ]
}
<?xml version="1.0" encoding="UTF-8"?><CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><CORSRule>    <AllowedOrigin>*</AllowedOrigin>    <AllowedMethod>POST</AllowedMethod>    <AllowedMethod>GET</AllowedMethod>    <AllowedMethod>PUT</AllowedMethod>    <AllowedMethod>DELETE</AllowedMethod>    <AllowedMethod>HEAD</AllowedMethod>    
<AllowedHeader>*</AllowedHeader>
</CORSRule></CORSConfiguration>

Step 3: Create a new IAM user

Once we have created the bucket, we can now concentrate on creating an IAM user. The quickest way to find the right page for the creation is to search within the AWS console.

AWS Konsole

The new user should only have access to the Get, Put and DeleteObjects. Here, too, security plays a major role. Should unauthorized persons gain access to the key, they still do not have full access to the bucket. It is therefore advisable to create a new policy.

Here is an example of a policy. All the steps for creating a new IAM user can be found in the following list:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::YOUR_BUCKET/*"
        }
    ]
}
  • Navigate to the user – the link is in the sidebar, click on Add user
  • Enter a name and select Programmatic access. This creates a separate access key for the user, which we will use later when uploading.
  • Select “Attach existing policies directly” to be able to create a new policy under “Create policy”. The button will open a new tab.
  • Simply insert the code from above under JSON and click “review Policy”. Note: Instead of YOUR_BUCKET you have to use the name of your bucket of course.
  • Assign a name for the policy and close the tab with “create policy”
  • Update the policies and select the newly created policy
  • Click “Next: Tags”, which you can ignore because we don’t need tags
  • After creating the user, you can see the key. You can either copy the text or download a CSV file. We will need this key in the next step!
AWS Konsole

The AWS JS SDK

After successfully setting up our AWS console, we can finally start coding. For reasons of reusability, I have put the AWS methods separately in an aws.js file.

First step: Create an S3 instance

First of all, we want to add the aws-sdk library to our project to be able to use the AWS method. We also use the axios package for requests.

Install aws-sdk and axios in your yarn project:

yarn add aws-sdk axios

As soon as the installation was successful, we can start with the initialization of an S3 instance. As you can see, we are using our access keys from the .env file and a region variable. An S3 instance can be initialized with new aws.S3 (). I have stated the option signatureVersion to be able to upload files to the server. If you use an American server, you can save yourself this option.

const aws = require('aws-sdk')

aws.config.update({
  secretAccessKey: process.env.VUE_APP_AWS_SECRET_ACCESS_KEY,
  accessKeyId: process.env.VUE_APP_AWS_ACCESS_KEY,
})

export const s3 = new aws.S3({
  signatureVersion: 'v4',
  region: process.env.VUE_APP_AWS_REGION,
})

Note: if you copy the code, don’t forget to define VUE_APP_AWS_ACCESS_KEY, VUE_APP_AWS_SECRET_ACCESS_KEY and VUE_APP_AWS_REGION in your .env variables.

Second step: SignedURL

When the configuration is finished, we can create our singleUpload method with signedURL. A quick look at signedURLs and why is it good to use them?

  • The signedURL can only be used for individual file uploads – the user cannot unintentionally fill the bucket further
  • The signedURL encrypts the filename and filetype – the user can only upload the registered file
  • The signedURL is limited in time – this protects against exploits e.g .: Users with bad intentions who try to use the signedURL from another user
  • The signedURL is generated by the Vue.js app and cannot be created by yourself
  • The signedURL only works with the specified bucket – users cannot see or access any other bucket
export const singleUpload = (file, folder) => {
  const key = folder + '/' + Date.now() + '-' + file.name.replace(/\s/g, '-')
  const params = {
    Bucket: process.env.VUE_APP_AWS_BUCKET,
    Key: key,
    Expires: 10,
    ContentType: file.type,
  }
  const url = s3.getSignedUrl('putObject', params)
  return axios
    .put(url, file, {
      headers: {
        'Content-Type': file.type,
      },
    })
    .then(result => {
      const bucketUrl = decodeURIComponent(result.request.responseURL).split(
        key
      )[0]
      result.key = key
      result.fullPath = bucketUrl + key
      return result
    })
    .catch(err => {
      // TODO: error handling
      console.log(err)
    })
}

In line 2 we generate a specific file name at AWS called a key. Also, the file name must also contain the folder in which the file should be located, for example, an album or team. We can delimit the folder name with a slash. We use Date.now () to generate a unique file name. The replace method replaces the whitespaces with a hyphen (-). It would even be possible to only work with Date.now (). This is up to you as to which structure you want to build in your bucket.

As I mentioned above, the “Expires” attribute limits the URL in time. If you want to learn more about getSignedUrl, click on the link.

As soon as the file has been uploaded, we receive the key and the link to the file, which we give back, for example, to store it in our database.

Third step: delete the file

Deleting an uploaded file is just as easy. All you need is the bucket and the name of the file. If you use several buckets, it is better to save the bucket name in the database as well. Both names can then be pulled from the database. After the successful deletion in the bucket, you must of course also delete the file from your database.

export const deleteObjectByKey = key => {
  const params = {
    Bucket: process.env.VUE_APP_AWS_BUCKET,
    Key: key,
  }
  const data = s3.deleteObject(params).promise()

  return data
}

Upload component in Vue with Filepond

If you don’t want to style your file upload yourself, filepond is highly recommended. With the library, you can implement a professional UI for uploading in a few minutes.

Upload Komponente

Step 1: FilePond component

To be able to use the libraries, we add them back to the project dependencies with yarn.

yarn add vue-filepont filepond-plugin-file-validate-type filepond-plugin-image-preview filepond-plugin-image-crop filepond-plugin-image-transform

After successfully adding it, you can import vue-filepond into the desired Vue component.

import vueFilePond from 'vue-filepond'
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
import FilePondPluginImagePreview from 'filepond-plugin-image-preview'
import FilePondPluginImageCrop from 'filepond-plugin-image-crop'
import FilePondPluginImageTransform from 'filepond-plugin-image-transform'
  <FilePond
    ref="pond"
      :server="{
      process: (fieldName, file, metadata, load, error, progress, abort) => {
        uploadFile(file, metadata, load, error, progress, abort)
      },
    }"
    @removefile="onRemoveFile"
  />

Now to the FilePond component: Ref is required to connect the method like processFiles, addFile, etc. with the component. When the file is edited, our uploadImages method is executed with the parameters. Important: the AWS methods must also be imported from aws.js.

import { singleUpload, deleteObjectByKey } from '@/aws.js'

Step 2: This is how it works with the file upload

The file upload in our Vue app can now be implemented quite easily. We call our uploadFile method with the file and the desired folder as parameters. If the upload was successful, we will receive a response with the status 200.

async uploadFile(file, metadata, load, error, progress, abort){
      const result = await singleUpload(
        file,
        this.$route.params.teamSlug // folder of the file, you should change it to your variable or a string
      )
      if (result.status === 200) {
        // Handle storing it to your database here
        load(file) // Let FilePond know the processing is done
      } else {
        error() // Let FilePond know the upload was unsuccessful
      }
      return {
        abort: () => {
          // This function is entered if the user has tapped the cancel button
          // Let FilePond know the request has been cancelled
          abort()
        },
      }
},

Step 3: rendering the pictures

To display files in your Vue app later, specific file data such as key and url must be saved in the database. If you only save images, then only the key is sufficient, since the URL can be generated in a computed object.

computed: {
  imgSrcArray: () => {
    return this.keys.map(url => 'https://s3.eu-central-1.amazonaws.com/vue-fileupload-example/' + url)
  },
},

Important: Exchange eu-central-1 for your bucket region and vue-fileupload-example for your bucket name! Then you can v-for render me a list of images, for example.

<img v-for="src in imgSrcArray" :src="src"/>

Step 4: removal of files

In step 1 you have probably already noticed the v-on remove. Now I will show you the method that will be performed when deleting.

async onRemoveFile(event) {
      let url = this.$route.params.teamSlug + '/' + event.file.name // event.file.name has only the name after the slash / so the actual filename
      const res = await deleteObjectByKey(url)
      if (
        res.$response.httpResponse.statusCode >= 200 &&
        res.$response.httpResponse.statusCode < 300
      ){
        // here remove data from database too
      }
    },

The status code of the response between 200 and 300 means that the file has either been deleted or does not exist at all.

Résumé

With the help of an AWS bucket and the signedURL function, it is relatively easy to implement a file upload without much involvement of the backend. So the backend is not put under unnecessary load. In connection with Vue and Filepond, the desired upload is ready for use via the frontend.