Blog

Upgrading a Vue TypeScript Project (2.x -> 3.x)

Migrating from Parcel to vite

BinHong Lee

December 12, 2021

While Vue 3 has been out of beta for quite a while, (as noted on their website) a migration build is still a work in progress so the current advice is to stay with Vue 2 if it’s a non-trivial project. I procrastinated on this article for so long that a migration build has since been released. Feel free to check it out here.

That said, as Parcel v1 is no longer maintained (and with v2 supporting only Vue 3), I figured it’s time to upgrade.

Why not Parcel v2?

This is actually quite straightforward. I’ve been wanting to try out vite for a while since I first saw it mentioned on an esbuild issue (asking for vue SFC support). I did consider snowpack for a while but I still decided to stick with vite seeing how its also created by Evan You and it seems to have mildly better performance.

I did initially try to upgrade the project to Parcel v2 seeing as how simple it was described as. However, I was wrong and instead of sinking anymore time into it, I decided to move onto something else.

Moving onto vite

The one thing that really sealed it for me (with vite) was how simple the move turned out to be. I created an empty project with their template, clone the relatively minimal default config over, and it’s “mostly” working. (Well, “mostly” because my project was still on Vue 2 instead of Vue 3.) Funnily enough, this was the same reason that led me to opting with Parcel initially since it needed very minimal configuration to get started.

vue-router

According to the official Vue 3 guide, the way to call the router is to import and call it the VueRouter you defined. This is slightly different from the previous version where you call this.router to use it. Well technically you can still do exactly that, as long as you define router in your global properties like below.

1
2
3
4
5
6
7
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

const app = createApp(App); 
app.config.globalProperties.$router = router;
app.use(router);

At this point, router navigation should work just fine but you might see tsc (or vue-tsc) throwing type errors saying that this.router is of type unknown. We’ll fix this later when discussing about vue-tsc.

vue-tsc

One of the main upgrades that Vue 3 provides is vue-tsc. On top of the regular type checking already available with tsc, vue-tsc also do type checking for inline TypeScript / Vue code in template (like v-for, v-if, :props…).

Volar (vscode)

If you do your Vue project development on vscode, you probably have Vetur installed. While Vetur works with Vue 3 projects, Volar is an upgrade to that comparatively as it uses vue-tsc and displays those new type errors inline. They do not complement / work with each other though so make sure you remove one before you install the other.

Shims

So if you’re not already familiar with shims, it’s essentially a type declaration file to tell your IDE what are the types of the vue file exports (and tsx files). Here’s a stackoverflow question on why it’s needed.

shims-vue.d.ts

This is the generic one that’d be generated when you create a new Vue project.

1
2
3
4
5
declare module "*.vue" {
  import { DefineComponent } from "vue";
  const component: DefineComponent;
  export default component;
}

shims-vue-run-time.d.ts

If you make router calls in your code with this.$router directly, you might find vue-tsc complaining about missing type declaration for it even if you already declared app.use(router); in your main.ts file. It seems like that doesn’t inherit the types imported automatically so you need to declare it separately for vue-tsc to know where to look.

1
2
3
4
5
6
7
import VueRouter from "vue-router";

declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    $router: VueRouter;
  }
}

Of course, if you also call other stuffs similarly. As an example, let’s say you call this.$notify with ElementPlus. You’d also include that in this type declaration file like this:

1
2
3
4
5
6
7
8
9
import { ElNotification } from "element-plus";
import VueRouter from "vue-router";

declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    $notify: ElNotification;
    $router: VueRouter;
  }
}

As for why I can’t just put these lines of codes in the same type declaration file as above, it threw me some error when I tried that. Separating them somehow solved the issue for me.

index.html

Usually this should just be in default state so the only change that needs to be made was to add this additional param of type="module" to the script tag calling the entrypoint.

1
<script type="module" src="/src/main.ts"></script>

Pug

For pug in Vue 2, you can have your whole block indented (kinda like the regular html block). But in Vue 3, it’s no longer supported so your outer most div can no longer be indented.

Wrap up

Overall, there are some bumps here and there but the process is relatively easy to understand especially with the help of new tools like volar (and faster compilation of vite).