loading...
Cover image for Building a Vue 3 component with Typescript

Building a Vue 3 component with Typescript

lmillucci profile image Lorenzo Millucci Updated on ・5 min read

The alpha version of Vue 3 has been available for some time now!
While I’m writing this post the alpha 8 version has just been released.

The new version of Vue will bring with it an avalanche of enhancements and improvements but the most significant changes in the new version will be:

  • Composition API: this is a much discussed feature inspired by the React hooks.
  • Portals: render certain content outside the current component.
  • Fragments: allows multiple root nodes.
  • Updated v-model-API: it now accepts multiple models.
  • Suspense: is a special component that renders a fallback content instead of your component until a condition is met (mostly for UX stuff).
  • TypeScript: Vue now has full TypeScript support.

Since Vue 3 is still under active development some of its components may still have bugs or some API may change but it is already possible to start writing a simple application to start playing with the new features.

Because I’m a big fan of TypeScript in this article I will describe you the steps that I followed to to create a new application with Vue 3 using TypeScript.
But talk is cheap, let’s start setting up a new app 🙂

Setting up the project

The first thing to do is to initialize a new application using the command on the next line:

yarn init

Next, add the dependencies needed by the project:

yarn add vue@3.0.0-alpha.8
yarn add --dev yarn vue-loader@v16.0.0-alpha.3 webpack-cli webpack webpack-dev-server typescript ts-loader @vue/compiler-sfc@v3.0.0-alpha.8

Now you have to define a simple webpack configuration creating the file webpack.config.js and adding the following code:

const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')

module.exports = (env = {}) => ({
  mode: env.prod ? 'production' : 'development',
  devtool: env.prod ? 'source-map' : 'cheap-module-eval-source-map',
  entry: path.resolve(__dirname, './src/main.ts'),
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: 'vue-loader'
      },
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
        }
      },
    ]
  },
  resolve: {
    extensions: ['.ts', '.js', '.vue', '.json'],
    alias: {
      'vue': '@vue/runtime-dom'
    }
  },
  plugins: [
    new VueLoaderPlugin(),
  ],
  devServer: {
    inline: true,
    hot: true,
    stats: 'minimal',
    contentBase: __dirname,
    overlay: true
  }
})

So far so good, our setup is coming together, but it won’t compile TypeScript just yet, for that we need to add a tsconfig.json file with the following rules:

{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "declaration": false,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "module": "es2015",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noLib": false,
    "sourceMap": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "suppressImplicitAnyIndexErrors": true,
    "target": "es2015",
    "baseUrl": ".",
  },
  "exclude": [
    "./node_modules"
  ],
  "include": [
    "./src/**/*.ts",
    "./src/**/*.vue",
  ],
}

After configuring Typescript and Webpack it is time to add a shortcut to start your application adding a new script to the package.json file:

{

  //...
  // Dependencies
  //...

  "scripts": {
    "dev": "webpack-dev-server"
  }
}

NOTE: to avoid error when you will import *.vue files you have to add the following shims-vue.d.ts file in ./src folder:

declare module "*.vue" {
    import { defineComponent } from "vue";
    const Component: ReturnType<typeof defineComponent>;
    export default Component;
}

To test that the infrastructure built so far is working correctly you will need some things:

  • Have a simple index.html in the root of the project
<!-- index.html -->
<h1>Hello Vue 3!</h1>
<script src="/dist/main.js"></script>
  • Have a /src folder
  • Have main.ts file to the project as follows:
// src/main.ts

console.log('Hello world from Typescript!');

  • Run the web server with yarn dev

If everything is working then connecting to http://localhost:8080 you should see the page we have just created.

Alt Text

To recap, at the end of these steps you should have the following architecture:

├── index.html
├── package.json
├── tsconfig.json
├── webpack.config.js
├── src
│    ├── shims-vue.d.ts
│    └── main.ts

Let’s build a component

Now that the environment needed to build the application is finally ready you can start creating your first Vue 3 component.

First of all, add a new file called App.vue inside the src folder as follows:

<template>
  <h2>This is a Vue 3 component!</h2>
  <button @click="increase">Clicked {{ count }} times.</button>
</template>
<script lang="ts">
import {defineComponent, ref} from "vue";
export default defineComponent({
  setup() {
    const count = ref(0)
    const increase = () => {
      count.value++
    }

    return {
      count,
      increase,
    }
  }
});
</script>

As you can see, compared to Vue 2 in which to create a new Vue component it was necessary to create a Typescript class and extend Vue using class MyClass extends Vue {}, now Vue 3 offers a defineComponent() function.
Inside the defineComponent() function you can see a setup function, which takes the props as the first argument. Since in this case the App component will be a top level component, no props will be passed (so I have omitted to pass them on).
Also, as you can see from the code, whatever is returned by the setup() method, it is then accessible from the template.

Now that the Vue component has been created, you just have to import it into the main.ts file as follows:

import {createApp} from 'vue';
import App from './App.vue';

createApp(App).mount('#app');

Also in this case you can see how compared to the previous version of Vue it is no longer necessary to initialize the new application with const app = new Vue (....).$Mount('# app') but with Vue 3 it is possible to use the function createApp() and the mount() method to bind the application to a DOM selector.

Finally, the last step is to edit the index.html file to include the selector specified to Vue in the previous step:

<h1>Hello Vue 3!</h1>

<div id="app"></div>

<script src="/dist/main.js"></script>

At this point, relaunching the application with yarn dev you can start playing with the new Vue component just created.

Alt Text

Recap

In this post I showed you how to create a very simple component using Vue 3, the composition API and Typescript. Obviously I have scratched just the tip of the iceberg and there are a thousand other features to try in Vue 3 but already with this simple component it is possible to appreciate the new "functional" approach with which it is possible to define the components in the next release of Vue.

PS: All the code is available on GitHub.


Feel free to reach out to me! Blog (in italian) || Twitter || GitHub || LinkedIn

Posted on by:

lmillucci profile

Lorenzo Millucci

@lmillucci

I'm a software engineer. Gamer on my free time :)

Discussion

pic
Editor guide
 

Thank you so much for this little head start. I was quite frustrated with Vue2 + vue-class-components and this was the last little spark I needed to finally try this setup. I failed with setting up the vue3 example with TS before because I didn't get the shims file right. Thanks for helping with this!

Just a little nag: Because I had experience with it, it was obvious for me, but your article doesn't obviously state where to put the shims file when it introduces it. This is fixed with the little file tree later but you might want to improve the wording around the shims file part as well.

 

Changed! Tank you for the feedback :)

 

I don't understand. You are writing TS in script tag. But I don't see you are using TS inside your setup function. You can write this without add typescript and just use setup(). So what is this blog about?

 

It is a very very simple example, it's true that I'm not using any specific Typescript code inside the setup function but I could have use it because the project is configured to transpile typescript.
This article was about configuring the environment to create a very easy component to try composition API through Typescript. It's clear that you can easily remove all the TS setup and go with the vanilla JS ;)

PS: if I had used only JS I would have defined a the component with export default { setup(){} } and not with export default defineComponent({ .. })

 

I see. Thank you for clearly that up for me. Good reading too.

 

What is needed to enable the ability to write styles/css into the App.vue file so that webpack properly extracts it? Is there a css loader of some sort that needs to be applied?

 

vue-loader should do the trick

 

We did have vue-loader, but no, it required other things like css-loader and file-loader for images too.

 
 

This weird, I forgot to wrap my setup() logic in a defineComponent and TS was still working ok in my Vue 3 code. Any ideas why that is?

 

Clear explanation.

Thanks for sharing.

 

When Vuex and Router would be fully rewritten in TS, only then it would be ready for production with TS. Now it's useless.

 

Typescript is not JavaScript.