DEV Community

George Hanson
George Hanson

Posted on • Updated on

Building VueJS Applications with TypeScript

TypeScript has undoubtedly grown in popularity over recent years. More and more developers within the web industry are looking to use static-type languages and with the release of Angular 2 back in 2016, this has only increased the demand for developers who use TypeScript.

When I started writing Angular applications with TypeScript, I thought it was great. I liked static-type checking systems and I liked being able to store the template outside of the TypeScript file, giving me separation from the logic and presentation layers. Unfortunately, I didn't like the fact that Angular requires so much set-up and not to mention the fact you had to do three or four different things to build one component. For me, it was too costly for time.

Prior to this I had used VueJS for building single page applications and I loved it. I always wanted to be able to bring TypeScript to VueJS and so the research began!

Now I found many tutorials that explained how TypeScript could be used with VueJS, but a lot of these were focusing on single file components. Which in my opinion works with using just JavaScript, but I really liked how Angular could store the template in an HTML file.

@Component({
    selector: 'my-dashboard',
    templateUrl: 'dashboard.component.html',
})
export class DashboardComponent {}
Enter fullscreen mode Exit fullscreen mode

When I thought I was out of luck, I found the solution to all of my problems. The amazing VueJS team have recently released Vue CLI 3 - which has made the process of writing TypeScript applications with VueJS so much easier! Let's take a look on how to setup a VueJS application using TypeScript.

Install Vue CLI

The first step is to install Vue CLI, to do this, simply run one of the following commands (depending on what tool you use).

npm install -g @vue/cli
# OR
yarn global add @vue/cli
Enter fullscreen mode Exit fullscreen mode

Once this is done, you can verify that it has installed correctly by running vue --version. It should display something like 3.0.1.

Create a TypeScript project

The new Vue CLI tool allows you to easily create new projects with TypeScript. To get started, simply run vue create my-app. You'll then be asked to choose a preset. Using your arrow keys, choose Manually select features.

Next, you just need to ensure you have selected the TypeScript and Babel options. You can see below that I have also selected some other optional features.

Select your options

Once you've done this, it will ask you if you would like to use the class-style component syntax. You will want to choose this option.

Then configure the rest of the settings so it should look like the below image.

Configuration options

The Vue CLI tool will now install all of the dependencies and set the project up.

Add Extra Dependencies

To achieve the affect we are after, there are a few extra dependencies that we need to install. You can install these by running one of the following commands.

npm install --save vue-template-loader webpack-stream
# OR
yarn add vue-template-loader webpack-stream
Enter fullscreen mode Exit fullscreen mode

You should now be able to run yarn serve to view your current application.

Using TypeScript classes instead of Single File Components

Next, we want to remove the need for .vue files and instead use TypeScript classes. Within the components directory, you can see there is HelloWorld.vue. We are going to re-create this with a TypeScript class instead.

So firstly, create a new file within the components directory and call it HelloWorld.ts. We'll add the following boilerplate to get started.

import { Component, Vue } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {

}
Enter fullscreen mode Exit fullscreen mode

This gives us a blank component ready to use. The first thing we need to do is have an external .html file for our presentation layer of the component. To do this, create a new file called hello-world.html. You can place this file wherever you want, but for demonstration purposes I will place it in the same folder as our component.

Now we need to copy across the presentation from the HelloWorld.vue component into our new hello-world.html file. So our file should now look like this.

<div class="hello">
    <h1>{{ msg }}</h1>
    <p>
      For guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>
    <h3>Installed CLI Plugins</h3>
    <ul>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
    </ul>
    <h3>Essential Links</h3>
    <ul>
      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
    </ul>
    <h3>Ecosystem</h3>
    <ul>
      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
    </ul>
  </div>
Enter fullscreen mode Exit fullscreen mode

So how do we use this template file within our HelloWorld.ts class? The extra dependencies we installed enable us to use another decorator, WithRender. This allows us to import our HTML file and tell our Vue component to use our file for rendering. After adding this to our TypeScript file, it should look like this.

import { Component, Vue } from 'vue-property-decorator';
import WithRender from './hello-world.html';

@WithRender
@Component
export default class HelloWorld extends Vue {

}
Enter fullscreen mode Exit fullscreen mode

Now we need to hook up the Vue Router to use our new TypeScript class instead of the HelloWorld.vue file. To do this, open the views/Home.vue file. Realistically you would also create a TypeScript class for this component as well, but for this guide, we will just edit it.

Within the file, change the import statement to use our TypeScript file instead. So we will change the following line from

import HelloWorld from '@/components/HelloWorld.vue'
Enter fullscreen mode Exit fullscreen mode

to

import HelloWorld from '@/components/HelloWorld.ts';
Enter fullscreen mode Exit fullscreen mode

However, if you now go to your browser, you will see there is an error. In our terminal we get the error:

Cannot find module './hello-world.html'
Enter fullscreen mode Exit fullscreen mode

This is because TypeScript doens't know how to handle .html files. So we need to add a shim file. To do this, within the src folder, create a shims-html.d.ts file. Paste the following code so your file should look like this:

declare module '*.html' {
    import Vue, { ComponentOptions, FunctionalComponentOptions } from 'vue'
    interface WithRender {
        <V extends Vue, U extends ComponentOptions<V> | FunctionalComponentOptions>(options: U): U
        <V extends typeof Vue>(component: V): V
    }
    const withRender: WithRender
    export default withRender
}
Enter fullscreen mode Exit fullscreen mode

Now we need to update our tsconfig.json file so that TypeScript knows to load .html files. Add the following line to the include array: "src/**/*.html". So it should look something like this:

  ...
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx",
    "src/**/*.html"
  ],
  ...
Enter fullscreen mode Exit fullscreen mode

Finally, we need to add some custom webpack configuration in the build process to tell Vue to pass the html file through its template compiler. To do this, within the root of your project create a vue.config.js file and add the following:

module.exports = {
  configureWebpack: {
    module: {
      rules: [
        {
          test: /.html$/,
          loader: "vue-template-loader",
          exclude: /index.html/
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Next, we need to restart the compilation process so TypeScript loads our changes. Close the current terminal process and run one of the following commands again.

npm run serve
# OR
yarn serve
Enter fullscreen mode Exit fullscreen mode

You should now see the application loading as it was before, this time it is using the TypeScript class file and the html template file.

One last thing you might notice is that the msg data property is no longer there. So let's add that now.

Within your HelloWorld.ts file, add the following property

public msg: string = 'I am using TypeScript classes with Vue!';
Enter fullscreen mode Exit fullscreen mode

If you now look back at your browser, you should now see this being rendered on the page.

That's all there is to it, you can now build your application using Vue, but using TypeScript classes and html file templates. While some people may disagree with this approach and argue you should only use .vue files, I find this
approach cleaner, especially when some of the files get really long.

This is the first part in a series. Next time I will go deeper into writing VueJS applications with TypeScript and explain methods, data attributes, props, child components and more!

UPDATE

Part two is now available - https://dev.to/georgehanson/building-vue-js-applications-with-typescript-part-two-2808

Discussion (28)

Collapse
aihowes profile image
Alex Howes • Edited

Glad I'm not the only one that likes separating template files.

Good to know about the use of a decorator to load in the template file - might give it a try and see what I prefer.

I've been using the below to load in my template files.

@Component({
    template: require('./template.html'),
})
export default class LinkComponent extends AbstractBEMComponent
{
...

Alternatively I've also been using the below:

import template from './template.html';

@Component({
    template
})
export default class LinkComponent extends AbstractBEMComponent
{
...

The second requires the below to be added to .d.ts file as mentioned in your post.

declare module "*.html" 
Collapse
georgehanson profile image
George Hanson Author

That's how I used to do it before, which in my opinion works pretty well. I just prefer the use of the decorator

Collapse
developersubash profile image
developer-subash

can you provide me gihub link please because it is not working on mine it says
You may need an appropriate loader to handle this file type. while compiling can anyone give me idea

Collapse
aihowes profile image
Alex Howes

Fair enough - I wasn't sure if it was missing out any magic behind the scenes as I'm not using vue-template-loader. :)

Collapse
adamdyson profile image
Adam Dyson

I had learnt Angular first and am now building an application in Vue. When planning the architecture of the application, I was wanting to separate the HTML, TS and SCSS following the same convention as Angular.

However I've since reverted back to single-file-components because I am yet to find an IDE (IntelliJ, VSCode) which can lint files when separated and recognise custom components in HTML.

Are you experiencing the same problems and have you overcome them?

Collapse
tcmartin24 profile image
Terry

Awesome article George!

If I created another component in your project above, call it "CustomComponent", how could I reference it from within the html template of hello-world.html? In other words, I'd like to add something like:

<CustomComponent />

inside of hello-world.html. How could I do that? I know you mentioned you'd write an article in the future that would mention child components, but I'd really like to learn how to do that ASAP because your article has inspired my team to try your approach immediately but I'm stuck on this question.

Thanks again for the excellent article,
T.

Collapse
georgehanson profile image
George Hanson Author

Thanks!

Keep checking back, I'm working on something soon! :)

In response to your question, you can do something like this:

import { Component, Vue } from 'vue-property-decorator';
import WithRender from './hello-world.html';
import CustomComponent from './CustomComponent.ts';

@WithRender
@Component({
    components: {
        CustomComponent
    }
})
export default class HelloWorld extends Vue {

That would then make it available for use within that particular component.

Collapse
georgehanson profile image
George Hanson Author

Hi Terry,

I've just published part two - dev.to/georgehanson/building-vue-j...

Collapse
gdeb profile image
Géry Debongnie

Hey George Hanson,

nice article. But now, I have two questions:

  • what about the styles? How do you write the css(scss/whatever) for your typescript components? And in particular, did you manage to have the styles scoped to the component?
  • Would you care to elaborate on the benefits of writing your components in 2/3 files instead of the one file approach? I am personally conflicted about that. I researched this topic, but could not find any strong argument. Most of it is about liking one approach more.
Collapse
georgehanson profile image
George Hanson Author

Hi there,

I generally prefer to keep styling away from these components and instead have SCSS files which are compiled separately. The reason I prefer this is that I'm not a massive fan of styles being dynamically injected into the page. But that's more of a personal preference.

With this approach, you write two files per component instead of one per component. Now this is mainly down to personal preference but here is why I like it. Some components can start to have more complicated logic and when you choose to start having service classes, models and follow design patterns it becomes harder to do within just one file. Everything starts to get messy and it can be difficult to read.

Collapse
gdeb profile image
Géry Debongnie • Edited

Thank you for your reply. I'll think about it

For your information, unless I am wrong, it looks like the build process of vue-cli 3 builds a css file, which is not dynamically injected. However, I think that it was not like that before, not sure though.

Collapse
nlysaght profile image
nlysaght

Hi George, Just following your article and thanks for posting it's really getting me started with TS & Vue and separating out the markup. I'm just having a slight error when building and running, I'm sure I must have missed something but can't see what it could be, I was wondering if you or one of your readers might have a suggestion.
I needed to remove some of the HTML formatting from the snippet below to get this post to pass.

Kind Regards
The error is
error in ./src/components/hello-world.html

Module parse failed: Unexpected token 1:0
You may need an appropriate loader to handle this file type.
div class="hello"
h1 msg /h1
p

Collapse
x1k profile image
Asaju Enitan • Edited

@georgehanson , can you please respond to this, I am also facing the same problem

Collapse
cybercussion profile image
Mark Statkus • Edited

Thanks for this. I externalized my Navbar, but the image used will now no longer load. I've tried just about every path under the sun ./assets, ../assets, ../../assets, ../../../assets, the @assets or ~assets or ~@assets and just seems something with the external "WithRender" maybe isn't getting picked up by Webpack (vue.config.js) now.
Any one else have this issue? I'm all but left with jamming it into SCSS now to just workaround it.

Collapse
yobyob profile image
Grover Sean Reyes

Hi, this is very very very informative. I would just like to ask about the difference in separating template to another file rather than just using Vue file? you know something like pros and cons, or is this just something like developer-preferences? Thanks :D

Collapse
georgehanson profile image
George Hanson Author

Hi there,

I'm glad you found it informative:)

In answer to your question, it really is all developer preference. One reason I prefer it is when using class style components it can get a bit messy IMO with the template in the same file.

So if you want to use .vue files then feel free, it is entirely up to you :)

Collapse
oliviermattei profile image
oliviermattei • Edited

Great article !!!

I'm starting with typescript, but I like the angular approach to initializing components.

Overloading @component to have templateUrl would be cool.
Or in your example, rather have an @template('./url') decorator;
I find it clearer than importing a withRender and directly doing @withRender.

However, how to manage scss files in the same way?

Collapse
floubadour profile image
Piotrek Dąbrowski

These screenshots are total disaster!

Collapse
georgehanson profile image
George Hanson Author

There are only two screenshots. Neither of which you need to read the contents of or pay much attention to.

Collapse
davepile profile image
davepile

Hi George,

Thanks for the article.

The first screenshot for vue project creation shows Babel unticked, the second screenshot shows selecting Yes to using Babel alongside TS.

Thread Thread
georgehanson profile image
George Hanson Author

Oops! I'll change this shortly 😁

Collapse
lexswed profile image
Lex Swed • Edited

Thank you, looks very nice :)

Collapse
georgehanson profile image
George Hanson Author

I'm glad you have found it helpful!

Collapse
sisodiyaashwini profile image
SisodiyaAshwini

template syntax error Cannot use as component root element because it may contain multiple nodes.

I'm getting this error :(

Collapse
gribchic profile image
gribchic

sorry. how can we add an external css/scss file to component?

Collapse
vit1 profile image
Vitaly Pinchuk

If we haven't .vue files (only .ts with imported .html template), how to test it with jest & @vue/test-utils?

Collapse
iampaoloxd profile image
Paolo

someone know how to test this kind of setup ?

Collapse
vit1 profile image
Vitaly Pinchuk

You can see it in my pet project github.com/viT-1/systemjs-ts-es6-v...