This package provides a TypeScript-based template checker for Vue.js 3.x.
This package does not aim for the widest compatibility but looks at the future of Vue and takes now as the starting point for further development.
Therefore, the minimal requirements to work with this are:
- Vue 3.x
- Vue CLI Service 5.x
- TypeScript 4.x
- WebPack 5.x
Older version are not (and will not be) supported. Time to move on!
First of all install as a dev dependency:
$ npm install --save-dev '@juit/vue-ts-checker'
Using this plugin should be as easy as a few configuration changes in your
vue.config.js
file. As a starting point:
const VueTsCheckerPlugin = require('@juit/vue-ts-checker').VueTsCheckerPlugin
module.exports = {
chainWebpack: (config) => {
// We don't need the "fork-ts-checker" plugin anymore, as "vue-ts-checker"
// also checks all of the TypeScript included in each compilation!
config.plugins.delete('fork-ts-checker')
// Let the "vue-ts-checker" plugin take care of checking Vue and TypeScript
config.plugin('vue-ts-checker').use(new VueTsCheckerPlugin())
}
}
There's nothing to configure, all the compilation and checking preferences
will be read from your tsconfig.json
file.
Just make sure that your Vue file have extension .vue
, and your TypeScript
files have extension .ts
, as hesoteric configurations are not supported
for now...
P.S. as in fork-ts-checker
we use a child process to asynchronously check
the sources, without blocking the main Webpack thread.
There's minimal support for command line usage, but I guess it's a good option to test this out without modifying your entire build:
$ npm install -g '@juit/vue-ts-checker'
$ vue-ts-checker
Generated 2 reports (has errors)
TS2365 [ERROR] Operator '+' cannot be applied to types 'K' and 'number'.
| at src/foo.vue line 13 col 164
|
| …… 4 ? 3 : 4} scroll-fadein-bottom delay-${$index + 1}00 scroll-reverse]
| ^^^^^^^^^^
TS1005 [ERROR] ',' expected.
| at src/bar.ts line 2 col 29
|
| export const foo: NO = 'bar' NO
| ^^
Checked 41 files in 4.334 sec
$
Install and when run vue-ts-checker
will analyse all the files in your
current directory, according to the include and exclude you specified
in your tsconfig.json
.
Basically, @vue/compiler-sfc
does most of the work for us.
Given a .vue
file like
<template>
<div :class="clazz"/>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return { clazz: 'myclass' }
}
})
</script>
The Vue SFC compiler prepares two scripts:
The first is basically the contents of <script>
:
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return { clazz: 'myclass' }
}
})
And the second is the render(...)
function:
export function render(_ctx: any) {
const _component_foo = _resolveComponent("foo")
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", { class: _ctx.clazz }, null, 2 /* CLASS */),
]))
}
The annotated type of the component instance passed to the render function
here is any
, which in most cases would work as this is generated code...
But when we want to check component and render function together all our
wonderful typing system is stripped out by that any
.
So, using some AST trickery, we rewrite the two script above to look somehow similar to this:
import { defineComponent } from 'vue'
const __component__ = defineComponent({
data() {
return { clazz: 'myclass' }
}
})
export function render(_ctx: InstanceType<typeof __component__>) {
const _component_foo = _resolveComponent("foo")
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", { class: _ctx.clazz }, null, 2 /* CLASS */),
]))
}
export default __component__
And now we can feed this to TypeScript which will do the proper type checking!
For TypeScript we rely on LanguageServices
(exactly as Vetur does), so we
can more easily interact with a pseudo file system, and for every .vue
file
(e.g. /dir/file.vue
) we feed three sources:
A shim /dir/file.vue/index.ts
containing something like:
import './render' // import the render function from <template>
export * from './script' // export whatever <script> is exporting
import _default_ from './script' // import the "export default" from <script>
export default _default_ // re-export our default
The original contents of <script>
are exposed as /dir/file.vue/script.ts
:
import { defineComponent } from 'vue'
export defineComponent({
data() {
return { clazz: 'myclass' }
}
})
And finally our <template>
render function is in /dir/file.vue/render.ts
:
import __component__ from './script'
export function render(_ctx: InstanceType<typeof __component__>) {
const _component_foo = _resolveComponent("foo")
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", { class: _ctx.clazz }, null, 2 /* CLASS */),
]))
}
export default __component__
Using three pseudo files simplifies quite a lot the job of preserving the
scopes between script and render function, and TypeScript automatically
tries to import /dir/file.vue/index.ts
when told to import /dir/file.vue
(as it doesn't recognize the .vue
extension).