Vetur Terminal Interface
Roppongi.vue #5
@ktsn
Core Team Member of Vue.js
目次
- Vetur とは
- テンプレート解析の現状
- Vetur Terminal Interface (VTI)
- VTI の実装
- 予定中の機能追加・改善
Vetur とは
シンタックスハイライト
各ブロックの言語機能を提供
テンプレート解析の現状
テンプレート式の解析
フラグを ON にして使う
Vetur > Experimental: Template Interpolation Service
Vetur Terminal Interface
(VTI)
CLI から
template
型チェック
Demo
npm install -g vti
vti diagnostics
https://vuejs.github.io/vetur/vti.html
インストール
テンプレートの型チェック
VTI の実装
Template Transformer
<template>
<div>{{ message }}</div>
</template>
<template>
<div>{{ message }}</div>
</template>
{
type: 'VElement',
range: [ 13, 37 ],
name: 'div',
startTag: {
type: 'VStartTag',
range: [ 13, 18 ],
selfClosing: false,
attributes: []
},
children: [{
type: 'VExpressionContainer',
range: [ 18, 31 ],
expression: {
type: 'Identifier',
range: [ 21, 28 ],
name: 'message'
}
}],
endTag: {
type: 'VEndTag',
range: [ 31, 37 ]
}
}
vue-eslint-parser AST
{
type: 'VElement',
range: [ 13, 37 ],
name: 'div',
startTag: {
type: 'VStartTag',
range: [ 13, 18 ],
selfClosing: false,
attributes: []
},
children: [{
type: 'VExpressionContainer',
range: [ 18, 31 ],
expression: {
type: 'Identifier',
range: [ 21, 28 ],
name: 'message'
}
}],
endTag: {
type: 'VEndTag',
range: [ 31, 37 ]
}
}
<div>
{{ message }}
</div>
import __Component from "./Test.vue";
import { __vlsRenderHelper, __vlsComponentHelper,
__vlsIterationHelper } from "vue-editor-bridge";
__vlsRenderHelper(__Component, function () {
__vlsComponentHelper(this, "div", {
props: {},
on: {},
directives: []
}, [
this.message
]);
});
{
type: 'VElement',
range: [ 13, 37 ],
name: 'div',
startTag: {
type: 'VStartTag',
range: [ 13, 18 ],
selfClosing: false,
attributes: []
},
children: [{
type: 'VExpressionContainer',
range: [ 18, 31 ],
expression: {
type: 'Identifier',
range: [ 21, 28 ],
name: 'message'
}
}],
endTag: {
type: 'VEndTag',
range: [ 31, 37 ]
}
}
TypeScript Code
{
"pos": -1,
"end": -1,
"kind": 195,
"expression": {
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___vlsRenderHelper"
},
"arguments": [
{
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___Component"
},
{
"pos": -1,
"end": -1,
"kind": 200,
"parameters": [],
"body": {
"pos": -1,
"end": -1,
"kind": 222,
"statements": []
}
}
]
}
TypeScript AST
import __Component from "./Test.vue";
import { __vlsRenderHelper, __vlsComponentHelper,
__vlsIterationHelper } from "vue-editor-bridge";
__vlsRenderHelper(__Component, function () {
__vlsComponentHelper(this, "div", {
props: {},
on: {},
directives: []
}, [
this.message
]);
});
<div> </div>
{{ message }}
import __Component from "./Test.vue";
import { __vlsRenderHelper, __vlsComponentHelper,
__vlsIterationHelper } from "vue-editor-bridge";
__vlsRenderHelper(__Component, function () {
__vlsComponentHelper(this, "div", {
props: {},
on: {},
directives: []
}, [
this.message
]);
});
import __Component from "./Test.vue";
import { __vlsRenderHelper, __vlsComponentHelper,
__vlsIterationHelper } from "vue-editor-bridge";
__vlsRenderHelper(__Component, function () {
__vlsComponentHelper(this, "div", {
props: {},
on: {},
directives: []
}, [
this.message
]);
});
不正な Location でクラッシュ
{
"pos": -1,
"end": -1,
"kind": 195,
"expression": {
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___vlsRenderHelper"
},
"arguments": [
{
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___Component"
},
{
"pos": -1,
"end": -1,
"kind": 200,
"parameters": [],
"body": {
"pos": -1,
"end": -1,
"kind": 222,
"statements": []
}
}
]
}
import __Component from "./Test.vue";
import { __vlsRenderHelper, __vlsComponentHelper,
__vlsIterationHelper } from "vue-editor-bridge";
__vlsRenderHelper(__Component, function () {
__vlsComponentHelper(this, "div", {
props: {},
on: {},
directives: []
}, [
this.message
]);
});
<template>
<div>{{ message }}</div>
</template>
message: 21–28
message: 256–268
Location ズレ問題
VTI の実装
Source Map Generation
{
"pos": -1,
"end": -1,
"kind": 195,
"expression": {
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___vlsRenderHelper"
},
"arguments": [
{
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___Component"
},
{
"pos": -1,
"end": -1,
"kind": 200,
"parameters": [],
"body": {
"pos": -1,
"end": -1,
"kind": 222,
"statements": []
}
}
]
}
{
type: 'VElement',
range: [ 13, 37 ],
name: 'div',
startTag: {
type: 'VStartTag',
range: [ 13, 18 ],
selfClosing: false,
attributes: []
},
children: [{
type: 'VExpressionContainer',
range: [ 18, 31 ],
expression: {
type: 'Identifier',
range: [ 21, 28 ],
name: 'message'
}
}],
endTag: {
type: 'VEndTag',
range: [ 31, 37 ]
}
}
vue-eslint-parser AST
TypeScript AST
ts.setSourceMapRange(tsNode, {
pos: 13,
end: 37
});
import __Component from "./Test.vue";
import { __vlsRenderHelper, __vlsComponentHelper,
__vlsIterationHelper } from "vue-editor-bridge";
__vlsRenderHelper(__Component, function () {
__vlsComponentHelper(this, "div", {
props: {},
on: {},
directives: []
}, [
this.message
]);
});
{
"pos": -1,
"end": -1,
"kind": 195,
"expression": {
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___vlsRenderHelper"
},
"arguments": [
{
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___Component"
},
{
"pos": -1,
"end": -1,
"kind": 200,
"parameters": [],
"body": {
"pos": -1,
"end": -1,
"kind": 222,
"statements": []
}
}
]
}
{
"pos": 139,
"end": 308,
"kind": 195,
"expression": {
"pos": 139,
"end": 158,
"kind": 75,
"escapedText": "___vlsRenderHelper"
},
"arguments": [
{
"pos": 159,
"end": 170,
"kind": 75,
"escapedText": "___Component"
},
{
"pos": 171,
"end": 307,
"kind": 200,
"parameters": [],
"body": {
"pos": 183,
"end": 307,
"kind": 222,
"statements": []
}
}
]
}
正しい Location を持つ AST
Location を持たない AST
{
"pos": -1,
"end": -1,
"kind": 195,
"expression": {
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___vlsRenderHelper"
},
"arguments": [
{
"pos": -1,
"end": -1,
"kind": 75,
"escapedText": "___Component"
},
{
"pos": -1,
"end": -1,
"kind": 200,
"parameters": [],
"body": {
"pos": -1,
"end": -1,
"kind": 222,
"statements": []
}
}
]
}
{
"pos": 139,
"end": 308,
"kind": 195,
"expression": {
"pos": 139,
"end": 158,
"kind": 75,
"escapedText": "___vlsRenderHelper"
},
"arguments": [
{
"pos": 159,
"end": 170,
"kind": 75,
"escapedText": "___Component"
},
{
"pos": 171,
"end": 307,
"kind": 200,
"parameters": [],
"body": {
"pos": 183,
"end": 307,
"kind": 222,
"statements": []
}
}
]
}
元の Location を持つ AST
変換後の Location を持つ AST
Source Map
message: [21, 28] – [256, 268]
ts.getSourceMapRange(tsNode)
ただの Source Map では不十分
'Hello'+message
'Hello' + message
'Hello' + message:
[0, 15] – [0, 17]
Source Map
'Hello':
message:
[8, 15] – [10, 17]
[0, 7] – [0, 7]
'Hello'+message
'Hello' + message
'Hello' + message:
[0, 15] – [0, 17]
Source Map
'Hello':
message:
[0, 1, 2, 3, 4, 5, 6, 7]
– [0, 1, 2, 3, 4, 5, 6, 7]
[8, 9, 10, 11, 12, 13, 14, 15]
– [10, 11, 12, 13, 14, 15, 16, 17]
予定中の機能追加・改善
Props の型チェック
Template Transformer の改善
<template>
<div v-if="post">
<button @click="onClick(post)">Click</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { Post } from './post'
export default Vue.extend({
data() {
return {
post: null as Post | null
}
},
methods: {
onClick(post: Post): void { /* ... */}
}
})
</script>
Post | null ✅
Post ✅
Post | null ❌
まとめ
- VTI により CLI からテンプレート型チェックが可能に
- テンプレート → TS 変換で型チェック
- Source Map を用いてエラー位置をマッピング
- Props の型チェック、チェックの精度向上を予定
Vetur Terminal Interface
By katashin