Webpacker, Vue 3, and TypeScript
Versions:
- Webpacker 5
- Rails 6
- Vue 3
Foreword
Jump to section titled: ForewordCompared 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
Jump to section titled: How to configure TypeScript in Vue 31. TS Loader
Jump to section titled: 1. TS LoaderTo install the TypeScript Loader, run:
yarn add ts-loader
# or npm ts-loader
2. TS Config
Jump to section titled: 2. TS ConfigIn 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
Jump to section titled: 3. Webpack LoaderAs 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
Jump to section titled: 4. ShimsTo 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
Jump to section titled: 5. Linters and IDE HelpersThis 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
Jump to section titled: UsageVue and TS
Jump to section titled: Vue and TSUsing 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
Jump to section titled: Type DeclarationsTypes 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
Jump to section titled: Vue Data and PropertiesType Assertions
Jump to section titled: Type AssertionsUsing 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
Jump to section titled: Vue Reactive Data Options APIWith 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
Jump to section titled: Vue Reactive Data Composition APITODO :)
Vue Properties
Jump to section titled: Vue PropertiesThe 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
Jump to section titled: Vue Computed Properties and Methods Options APIComputed 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?
Jump to section titled: Eh, and now?To learn how to use TypeScript ➡️ (e)Book TypeScript in 50 lessons