Webpacker, Vue 3, and TypeScript
Vannsl
Posted on March 17, 2021
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
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"
]
}
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/,
};
In the webpack configuration config/environment.js
add the loader:
const ts = require("./loaders/ts");
// ...
environment.loaders.prepend("ts", ts);
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;
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
}
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:
- Add
lang="ts"
to the<script>
tag - Import
defineComponent
fromvue
- Export the component as
defineComponent
Example:
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
// ...
})
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;
// ...
}
Import types from that file:
import { Item } from "@/types";
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"
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>
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
}
}
}
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]
}
}
})
Eh, and now?
To learn how to use TypeScript ā”ļø (e)Book TypeScript in 50 lessons
Posted on March 17, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.