Webpacker, Vue 3, and TypeScript

vannsl

Vannsl

Posted on March 17, 2021

Webpacker, Vue 3, and TypeScript

Versions:

Webpacker 5
Rails 6
Vue 3


Foreword

Compared to Vue 2, Vue 3 is written in TypeScript. As we're used to, the official Vue Documentation is one of the best sources to find out more about how to configure TypeScript in Vue 3. Something that can bother is that most tutorials use the Vue CLI to show how simple TypeScript can be added to the codebase. Although the Vue CLI is a powerful tool and it is actually as simple as running one command to add TypeScript, not each project has the possibility to be configured with the CLI or Vite. This article explains how to add TypeScript to Vue 3 applications within Webpacker, the Webpack gem for Ruby on Rails Fullstack applications.

How to configure TypeScript in Vue 3

1. TS Loader

To install the TypeScript Loader, run:

yarn add ts-loader
# or npm ts-loader
Enter fullscreen mode Exit fullscreen mode

2. TS Config

In the root directory of the Rails App, create the file tsconfig.json. The following JSON is an example of the configuration you could add. Of course, your settings might differ from those. Make sure to adapt the paths which files to include in the TypeScript Compilation (app/javascript/src/**/*.ts and app/javascript/src/**/*.vue) depending on your folder structure. Same for the alias in the paths settings (app/javascript/src/*)

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "app/javascript/src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "app/javascript/src/**/*.ts",
    "app/javascript/src/**/*.vue",
  ],
  "exclude": [
    "node_modules"
  ]
}
Enter fullscreen mode Exit fullscreen mode

HAVE YOU CHECKED THE PATHS? NO? READ ABOVE THE CONFIG AFTER COPY/PASTING! ;)

3. Webpack Loader

As explained in a previous article about How to add Vue 3 in Rails I put all webpack loaders in a folder called config/webpack/loaders. You can also create your loaders inline.

The Loader Configuration is:

module.exports = {
  test: /\.tsx$/,
  loader: "ts-loader",
  options: {
    appendTsSuffixTo: [/\.vue$/],
  },
  exclude: /node_modules/,
};
Enter fullscreen mode Exit fullscreen mode

In the webpack configuration config/environment.js add the loader:

const ts = require("./loaders/ts");

// ...

environment.loaders.prepend("ts", ts);
Enter fullscreen mode Exit fullscreen mode

Only for reference, this is how my full webpack configuration looks like:

const { environment } = require("@rails/webpacker");
const { VueLoaderPlugin } = require("vue-loader");
const { DefinePlugin } = require("webpack");
const path = require("path");
const vue = require("./loaders/vue");
const ts = require("./loaders/ts");

const customConfig = {
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "..", "..", "app/javascript/src"),
      "~libs": path.resolve(__dirname, "..", "..", "app/javascript/lib"),
      "~images": path.resolve(__dirname, "..", "..", "app/javascript/images"),
    },
  },
};

environment.config.merge(customConfig);

environment.plugins.prepend("VueLoaderPlugin", new VueLoaderPlugin());
environment.plugins.prepend(
  "Define",
  new DefinePlugin({
    __VUE_OPTIONS_API__: false,
    __VUE_PROD_DEVTOOLS__: false,
  })
);
environment.loaders.prepend("ts", ts);
environment.loaders.prepend("vue", vue);
environment.splitChunks();
module.exports = environment;
Enter fullscreen mode Exit fullscreen mode

4. Shims

To get the TypeScript Support working in Vue Single File Components, they have to be defined as a component. Quoting the official documentation about defineCompinent:

Implementation-wise defineComponent does nothing but return the object passed to it. However, in terms of typing, the returned value has a synthetic type of a constructor for manual render function, TSX, and IDE tooling support.

In your folder where your Vue Applications are located (e.g. app/javascript/src), add the file shims-vue.d.ts to add the Shim:

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
Enter fullscreen mode Exit fullscreen mode

5. Linters and IDE Helpers

This is up to you. I use ESLint and Prettier. For IDE Support I switched from Vetur to Vue DX, but I can't strongly agree that you should do the same. The third member of the IDE Party is Volar, which I can totally recommend for pure Vue3+TS Projects, especially if you use the <script setup> syntactic sugar for using the Composition API. Try them out and check what works best for you.

šŸŽ‰ You're done!

Usage

Vue and TS

Using Typescript in .vue files require the following steps:

  1. Add lang="ts" to the <script> tag
  2. Import defineComponent from vue
  3. Export the component as defineComponent

Example:

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  // ...
})
Enter fullscreen mode Exit fullscreen mode

Type Declarations

Types can be found in file types.ts the source directory:

// app/javascript/src/types.ts

export interface Item {
  id: number;
  name?: string;
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Import types from that file:

import { Item } from "@/types";
Enter fullscreen mode Exit fullscreen mode

Vue Data and Properties

Type Assertions

Using the as keyword objects were able to override empty objects types to a real type:

const futureItem = {} as Item

futureItem.id = 1;
futureItem.name = "New Item"
Enter fullscreen mode Exit fullscreen mode

Vue Reactive Data Options API

With that, we can assign types to data attributes in .vue files:

<script lang="ts">
import { Item } from "@/types";
import { defineComponent } from "vue";

export default defineComponent({
  data() {
    return {
      items: [] as Item[],
      currentItem: {} as Item
    }
  }
})
</script>
Enter fullscreen mode Exit fullscreen mode

Vue Reactive Data Composition API

TODO :)

Vue Properties

The same does not simply work for Vue Properties. Using the PropType, Generics are set for custom properties.

// Before
props: {
  item: {
    type: Object,
    required: true
  }
}

// Won't work
props: {
  item: {
    type: Item, // this is not valid JavaScript
    required: true
  }
}

// Won't work
props: {
  item: {
    type: Object as Item, // valid JavaScript, but no generic
    required: true
  }
}

// Works
import { defineComponent, PropType} from "vue";
import { Item } from "@/types";

export default defineComponent({
  props: {
    item: {
      type: Object as PropType<Item>,
      required: true
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Vue Computed Properties and Methods Options API

Computed Properties and Methods don't need special TypeScript Handling in Vue.
Types can be applied as usual in TypeScript:

import { defineComponent, PropType} from "vue";
import { Item } from "@/types";

export default defineComponent({
  data() {
    return {
      items: [] as Item[],
      currentItem: {} as Item
    }
  },
  // Method Parameter types
  methods: {
    function addItem(newItem: Item) {
      this.items.push(newItem)
    }
  },
  // Computed Property Return Item
  computed: {
    firstItem(): Item {
      return this.items[0]
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Eh, and now?

To learn how to use TypeScript āž”ļø (e)Book TypeScript in 50 lessons

šŸ’– šŸ’Ŗ šŸ™… šŸš©
vannsl
Vannsl

Posted on March 17, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related