Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Meta Library VS Meta Type Definitions

Takepepe
July 10, 2019

Meta Library VS Meta Type Definitions

Takepepe

July 10, 2019
Tweet

More Decks by Takepepe

Other Decks in Technology

Transcript

  1. Meta Library VS
    Meta Type Definitions
    TypeScript Meetup #2 @Takepepe

    View Slide

  2. About Me
    ■ Takefumi Yoshii / @Takepepe
    ■ DeNA / DeSC Healthcare
    ■ Frontend Engineer
    ■ 「型の強化書・実践 TypeScript」著者

    View Slide

  3. 実践 TypeScript
    発売から一週間で増刷確定しました!
    Amazon「ITコンピュータ・IT 関連」で
    干し芋のランキング1位獲得
    (一時的)

    View Slide

  4. Agenda
    「実践 TypeScript」に残した課題を、
    「TypeScript Compiler API」で解決します。

    View Slide

  5. Agenda
    掲題の「Meta Library」は
    Vue.js でお馴染みの「Vuex」のこと。
    ■ 5分でわかる Vuex の概要と型課題
    ■ 推論観点で紐解く Vuex に必要な型定義
    ■ TypeScript Compiler API で壁を超える

    View Slide

  6. 5分でわかる Vuex の概要と型課題

    View Slide

  7. Vuex - State management -
    ■ 状態管理ライブラリの Vuex。Nuxt.js は標準で入る。
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts

    View Slide

  8. Vuex - State management -
    ■ 関心の境界で Module を定義
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts

    View Slide

  9. Vuex - State management -
    ■ Module は state を保持
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts
    store
    ├── counter
    │ └── { count: number }
    └── todos
    └── { todos: Todo[] }

    View Slide

  10. Vuex - State management -
    ■ todos の実装を確認していきます
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts
    store
    ├── counter
    │ └── { count: number }
    └── todos
    └── { todos: Todo[] }

    View Slide

  11. Vuex - Inside Module -
    ■ Module が保持する state ファクトリ関数
    export const state = () => ({ todos: [] })
    EX : store/todos/index.ts

    View Slide

  12. Vuex - Inside Module -
    ■ 算出プロパティを実装する getters
    export const state = () => ({ todos: [] })
    export const getters = {
    todosCount(state) {
    return state.todos.length
    },
    doneCount(state) {
    return state.todos.filter(todo => todo.done).length
    }
    }
    EX : store/todos/index.ts

    View Slide

  13. Vuex - Inside Module -
    ■ 自動で引数に挿さる参照がたくさん
    export const state = () => ({ todos: [] })
    export const getters = {
    todosCount(state, getters, rootState, rootGetters) {
    const counterCount = rootState.counter.count
    const counterDouble = rootGetters["counter/double"]
    return state.todos.length
    },
    doneCount(state, getters, rootState, rootGetters) {
    return state.todos.filter(todo => todo.done).length
    }
    }
    EX : store/todos/index.ts

    View Slide

  14. Vuex - Inside Module -
    ■ 自動で挿さる参照は4つ
    export const state = () => ({ todos: [] })
    export const getters = {
    todosCount(state, getters, rootState, rootGetters) {
    const counterCount = rootState.counter.count
    const counterDouble = rootGetters["counter/double"]
    return state.todos.length
    },
    doneCount(state, getters, rootState, rootGetters) {
    return state.todos.filter(todo => todo.done).length
    }
    }
    第一引数:state インスタンス参照(local)
    第二引数:getter 関数同士の参照(local)
    第三引数:store state の root 参照
    第四引数:getter 関数 の root 参照
    EX : store/todos/index.ts

    View Slide

  15. Vuex - Inside Module -
    ■ ファクトリ関数で生成された state インスタンス
    export const state = () => ({ todos: [] })
    export const getters = {
    todosCount(state, getters, rootState, rootGetters) {
    const counterCount = rootState.counter.count
    const counterDouble = rootGetters["counter/double"]
    return state.todos.length
    },
    doneCount(state, getters, rootState, rootGetters) {
    return state.todos.filter(todo => todo.done).length
    }
    }
    第一引数:state インスタンス参照(local)
    第二引数:getter 関数同士の参照(local)
    第三引数:store state の root 参照
    第四引数:getter 関数 の root 参照
    EX : store/todos/index.ts

    View Slide

  16. Vuex - Inside Module -
    ■ Module 内部の getter 関数参照
    export const state = () => ({ todos: [] })
    export const getters = {
    todosCount(state, getters, rootState, rootGetters) {
    const counterCount = rootState.counter.count
    const counterDouble = rootGetters["counter/double"]
    return state.todos.length
    },
    doneCount(state, getters, rootState, rootGetters) {
    return state.todos.filter(todo => todo.done).length
    }
    }
    第一引数:state インスタンス参照(local)
    第二引数:getter 関数同士の参照(local)
    第三引数:store state の root 参照
    第四引数:getter 関数 の root 参照
    EX : store/todos/index.ts

    View Slide

  17. Vuex - Inside Module -
    ■ rootState は他 Module への参照を持つ
    export const state = () => ({ todos: [] })
    export const getters = {
    todosCount(state, getters, rootState, rootGetters) {
    const counterCount = rootState.counter.count
    const counterDouble = rootGetters["counter/double"]
    return state.todos.length
    },
    doneCount(state, getters, rootState, rootGetters) {
    return state.todos.filter(todo => todo.done).length
    }
    }
    第一引数:state インスタンス参照(local)
    第二引数:getter 関数同士の参照(local)
    第三引数:store state の root 参照
    第四引数:getter 関数 の root 参照
    EX : store/todos/index.ts

    View Slide

  18. Vuex - Inside Module -
    ■ rootGetters は他 Module への参照を持つ
    export const state = () => ({ todos: [] })
    export const getters = {
    todosCount(state, getters, rootState, rootGetters) {
    const counterCount = rootState.counter.count
    const counterDouble = rootGetters["counter/double"]
    return state.todos.length
    },
    doneCount(state, getters, rootState, rootGetters) {
    return state.todos.filter(todo => todo.done).length
    }
    }
    第一引数:state インスタンス参照(local)
    第二引数:getter 関数同士の参照(local)
    第三引数:store state の root 参照
    第四引数:getter 関数 の root 参照
    EX : store/todos/index.ts

    View Slide

  19. Vuex - Literal Reference -
    ■ getter 関数の文字列参照
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts
    store
    ├── counter
    │ ├── function double(): number
    │ └── function expo(): number
    └── todos
    ├── function todosCount(): number
    └── function doneCount(): number

    View Slide

  20. Vuex - Literal Reference -
    ■ getter 関数の文字列参照(ツリー構造に起因)
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts
    store
    ├── counter
    │ ├── "counter/double"
    │ └── "counter/expo"
    └── todos
    ├── "todos/todosCount"
    └── "todos/doneCount"

    View Slide

  21. Vuex - Literal Reference -
    ■ getter 関数の文字列参照(ツリー構造に起因)
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts
    store
    ├── counter
    │ ├── "counter/double": number
    │ └── "counter/expo": number
    └── todos
    ├── "todos/todosCount": number
    └── "todos/doneCount": number
    getter 関数は、関数ではなく値として見える

    View Slide

  22. Vuex - Literal Reference -
    ■ getter 関数の文字列参照(SFC [Component] から)
    get double() {
    return this.$store.getters['counter/double']
    }
    get expo2() {
    return this.$store.getters['counter/expo2']
    }
    EX : components/example.vue

    View Slide

  23. Vuex - Literal Reference -
    ■ getter 関数の文字列参照(型推論が any …orz)
    get double() {
    return this.$store.getters['counter/double'] // any
    }
    get expo2() {
    return this.$store.getters['counter/expo2'] // any
    }
    EX : components/example.vue

    View Slide

  24. Vuex - Literal Reference -
    ■ store.commit 関数の文字列参照
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts
    store
    ├── counter
    │ ├── function setCount(n: number): void
    │ ├── function decrement(): void
    │ └── function increment(): void
    └── todos
    ├── function addTodo(todo: Todo): void
    └── function doneTodo(id: string): void
    「mutation」という状態を変更する関数群

    View Slide

  25. Vuex - Literal Reference -
    ■ store.commit 関数の文字列参照(ツリー構造に起因)
    store
    ├── counter
    │ └── index.ts
    └── todos
    └── index.ts
    store
    ├── counter
    │ ├── "counter/setCount"
    │ ├── "counter/decrement"
    │ └── "counter/increment"
    └── todos
    ├── "todos/addTodo"
    └── "todos/doneTodo"
    生成された一意な文字列は「mutation type」と呼ばれる

    View Slide

  26. Vuex - Literal Reference -
    ■ store.commit 関数の文字列参照(SFC [Component] から)
    addTodo() {
    this.$store.commit('todos/addTodo',{
    todo: {
    id: uuid(),
    createdAt: new Date(),
    task: this.todoTask,
    done: false
    }
    })
    }
    doneTodo() {
    this.$store.commit('todos/doneTodo',{
    id: this.id
    })
    }
    EX : components/example.vue

    View Slide

  27. Vuex - Literal Reference -
    ■ store.commit 関数の文字列参照(型推論が any …orz)
    addTodo() {
    this.$store.commit('todos/addTodo',{
    todo: {
    id: uuid(),
    createdAt: new Date(),
    task: this.todoTask,
    done: false
    }
    })
    }
    doneTodo() {
    this.$store.commit('todos/doneTodo',{
    id: this.id
    })
    }
    EX : components/example.vue

    View Slide

  28. Vuex - Type Issues -
    ■ 勘所が any に落ちている
    ■ 文字列参照と payload の「組」が担保できていない
    ■ Generics による補正も効かない
    ■ Module 名を変更しようものなら、影響範囲が甚大

    View Slide

  29. 公式型定義では無理がある。
    これはまずい、何とかしたい。

    View Slide

  30. TypeScript の「型推論」観点
    +
    「メタ型定義」で挑む

    View Slide

  31. 推論観点で紐解く Vuex に必要な型定義

    View Slide

  32. Getters ❌ state, ❌ getters, ❌ rootState, ❌ rootGetters
    第一引数 State 型「 type S 」
    export const getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    export type S = { todos: Todo[] }

    View Slide

  33. Getters ❌ state, ❌ getters, ❌ rootState, ❌ rootGetters
    getters 向け「 type Getters 」
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    }
    export type S = { todos: Todo[] }

    View Slide

  34. Getters ✅ state, ❌ getters, ❌ rootState, ❌ rootGetters
    第一引数を「Index Signature」で一律付与
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [k: string]: (state: S) => unknown
    }
    export type S = { todos: Todo[] }

    View Slide

  35. Getters ✅ state, ❌ getters, ❌ rootState, ❌ rootGetters
    getter 関数のメタ型定義「 type G 」
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [k: string]: (state: S, getters: G) => unknown
    }
    export type S = { todos: Todo[] }
    export type G = {
    }

    View Slide

  36. Getters ✅ state, ❌ getters, ❌ rootState, ❌ rootGetters
    外部参照に向けた「期待型」を定義
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [k: string]: (state: S, getters: G) => unknown
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }

    View Slide

  37. Getters ✅ state, ❌ getters, ❌ rootState, ❌ rootGetters
    外部参照に向けた「期待型」を定義
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [k: string]: (state: S, getters: G) => unknown
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    getter 関数は外からは値として見える

    View Slide

  38. Getters ✅ state, ✅ getters, ❌ rootState, ❌ rootGetters
    戻り型を「Mapped Types」で特定( G[K] )
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (state: S, getters: G) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    getter 関数は期待型を返す実装をしなければならない

    View Slide

  39. Getters ✅ state, ✅ getters, ❌ rootState, ❌ rootGetters
    ツリー構造に則した「 type RootState 」
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    type RootState = {
    }

    View Slide

  40. Getters ✅ state, ✅ getters, ✅ rootState, ❌ rootGetters
    型を import して組み立てる
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }

    View Slide

  41. Getters ✅ state, ✅ getters, ✅ rootState, ❌ rootGetters
    文字列参照のメタ型定義「 type RG 」
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }

    View Slide

  42. Getters ✅ state, ✅ getters, ✅ rootState, ❌ rootGetters
    文字列参照に「Indexed Access Types」でマッピング
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }

    View Slide

  43. Getters ✅ state, ✅ getters, ✅ rootState, ❌ rootGetters
    文字列参照を集約した「 type RootGetters 」
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters = {}

    View Slide

  44. Getters ✅ state, ✅ getters, ✅ rootState, ✅ rootGetters
    型を import して組み立てる
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters =
    Todos.RG & Counter.RG

    View Slide

  45. Getters ✅ state, ✅ getters, ✅ rootState, ✅ rootGetters
    All Green ! だがしかし…
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters =
    Todos.RG & Counter.RG

    View Slide

  46. Getters ✅ state, ✅ getters, ✅ rootState, ✅ rootGetters
    定常開発で「メタ型定義」がこれだけ必要…
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters =
    Todos.RG & Counter.RG

    View Slide

  47. Getters ✅ state, ✅ getters, ✅ rootState, ✅ rootGetters
    ヒューマンエラーが防げず、スマートではない…
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters =
    Todos.RG & Counter.RG

    View Slide

  48. Getters ✅ state, ✅ getters, ✅ rootState, ✅ rootGetters
    ツリー構造に則した型推論は不可能では…
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters =
    Todos.RG & Counter.RG

    View Slide

  49. 「実践TypeScript」にはここまで書いた。
    これが残してきた課題。

    View Slide

  50. 引用 : 実践 TypeScript(p.305)

    View Slide

  51. いやできる

    View Slide

  52. TypeScript Compiler API で壁を超える

    View Slide

  53. DEMO
    https://vimeo.com/346283058
    https://github.com/takefumi-yoshii/vuex-definitions-mapper

    View Slide

  54. Inside Codegen
    1. Watch Store Dir
    2. ts.Program
    3. ts.SourceFile
    4. ts.PropertySignature
    5. ts.TypeAliasDeclaration
    6. ts.NodeArray
    7. ts.Printer
    8. fs.writeFileSync

    View Slide

  55. 1. Watch Store Dir
    型構築に必要な情報を収集
    store
    ├── counter
    │ ├── index.ts
    │ └── type.ts
    ├── index.ts
    ├── todos
    │ ├── index.ts
    │ └── type.ts
    └── type.ts

    View Slide

  56. 1. Watch Store Dir
    DEMO では「type.ts」の変更を検知
    store
    ├── counter
    │ ├── index.ts
    │ └── type.ts
    ├── index.ts
    ├── todos
    │ ├── index.ts
    │ └── type.ts
    └── type.ts
    [
    { fileName: 'type.ts',
    filePath: '~/store/counter/type.ts',
    namespace: 'counter',
    moduleName: 'COUNTER' },
    { fileName: 'type.ts',
    filePath: '~/store/todos/type.ts',
    namespace: 'todos',
    moduleName: 'TODOS' }
    ]

    View Slide

  57. 1. Watch Store Dir
    普通の Node.js コード
    // 定義対象ディレクトリから、定義対象ファイル一覧を取得
    const [typeFiles, fileTree] = await getTypeDefinitions(storeDir, 'type.ts')

    View Slide

  58. 2. ts.Program
    「ts.Program」を作成、AST を取得・Compiler API を扱う
    // 定義対象ディレクトリから、定義対象ファイル一覧を取得
    const [typeFiles, fileTree] = await getTypeDefinitions(storeDir, 'type.ts')
    // program 用に配列を作成
    const files = typeFiles.map(file => file.filePath)
    // AST が格納された program を作成。(files: string[])
    const program = ts.createProgram(files, {})
    ts.createProgram 第一引数は src ファイルパスの文字列配列

    View Slide

  59. 3. ts.SourceFile
    「ts.SourceFile」を ts.Program から取得
    function typedefs(program: ts.Program, typeFile: TypeFile, typeKind: string) {
    const sourceFile = program.getSourceFile(typeFile.filePath)
    if (!sourceFile) return
    const typeDefinitions = sourceFile.getChildAt(0)
    return flatten(
    typeDefinitions
    .getChildren()
    .filter(
    (node): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration =>
    ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)
    )
    .filter(node => node.name.text === typeKind)
    .map(node => mapMembersTypeElements(node, typeFile, typeKind))
    )
    }

    View Slide

  60. 3. ts.SourceFile
    型定義に限定した「ts.Node」に絞り込む
    function typedefs(program: ts.Program, typeFile: TypeFile, typeKind: string) {
    const sourceFile = program.getSourceFile(typeFile.filePath)
    if (!sourceFile) return
    const typeDefinitions = sourceFile.getChildAt(0)
    return flatten(
    typeDefinitions
    .getChildren()
    .filter(
    (node): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration =>
    ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)
    )
    .filter(node => node.name.text === typeKind)
    .map(node => mapMembersTypeElements(node, typeFile, typeKind))
    )
    }
    ユーザー定義 type guard で絞り込む

    View Slide

  61. 3. ts.SourceFile
    規定名称(ex: type G or interface G )に一致した型宣言に絞り込む
    function typedefs(program: ts.Program, typeFile: TypeFile, typeKind: string) {
    const sourceFile = program.getSourceFile(typeFile.filePath)
    if (!sourceFile) return
    const typeDefinitions = sourceFile.getChildAt(0)
    return flatten(
    typeDefinitions
    .getChildren()
    .filter(
    (node): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration =>
    ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)
    )
    .filter(node => node.name.text === typeKind)
    .map(node => mapMembersTypeElements(node, typeFile, typeKind))
    )
    }
    node.name.text は src の型宣言名称

    View Slide

  62. 3. ts.SourceFile
    変換関数で処理、型プロパティシグネチャ配列をつくる
    function typedefs(program: ts.Program, typeFile: TypeFile, typeKind: string) {
    const sourceFile = program.getSourceFile(typeFile.filePath)
    if (!sourceFile) return
    const typeDefinitions = sourceFile.getChildAt(0)
    return flatten(
    typeDefinitions
    .getChildren()
    .filter(
    (node): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration =>
    ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)
    )
    .filter(node => node.name.text === typeKind)
    .map(node => mapMembersTypeElements(node, typeFile, typeKind))
    )
    }

    View Slide

  63. 生成したい型定義
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };

    View Slide

  64. ts.PropertySignature
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    4. ts.PropertySignature

    View Slide

  65. 4. ts.PropertySignature
    ts.createStringLiteral
    ts.createPropertySignature(
    undefined,
    ts.createStringLiteral(`${typeFile.namespace}/${identifier}`),
    undefined,
    ts.createIndexedAccessTypeNode(
    ts.createTypeReferenceNode(
    ts.createQualifiedName(
    ts.createIdentifier(typeFile.moduleName),
    ts.createIdentifier(typeKind)
    ),
    undefined
    ),
    ts.createLiteralTypeNode(ts.createStringLiteral(identifier))
    ),
    undefined)
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };

    View Slide

  66. 4. ts.PropertySignature
    ts.createStringLiteral
    ts.createPropertySignature(
    undefined,
    ts.createStringLiteral(`${typeFile.namespace}/${identifier}`),
    undefined,
    ts.createIndexedAccessTypeNode(
    ts.createTypeReferenceNode(
    ts.createQualifiedName(
    ts.createIdentifier(typeFile.moduleName),
    ts.createIdentifier(typeKind)
    ),
    undefined
    ),
    ts.createLiteralTypeNode(ts.createStringLiteral(identifier))
    ),
    undefined)
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    String Literal の生成!!

    View Slide

  67. 4. ts.PropertySignature
    ts.createQualifiedName
    ts.createPropertySignature(
    undefined,
    ts.createStringLiteral(`${typeFile.namespace}/${identifier}`),
    undefined,
    ts.createIndexedAccessTypeNode(
    ts.createTypeReferenceNode(
    ts.createQualifiedName(
    ts.createIdentifier(typeFile.moduleName),
    ts.createIdentifier(typeKind)
    ),
    undefined
    ),
    ts.createLiteralTypeNode(ts.createStringLiteral(identifier))
    ),
    undefined)
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };

    View Slide

  68. 4. ts.PropertySignature
    ts.createIndexedAccessTypeNode
    ts.createPropertySignature(
    undefined,
    ts.createStringLiteral(`${typeFile.namespace}/${identifier}`),
    undefined,
    ts.createIndexedAccessTypeNode(
    ts.createTypeReferenceNode(
    ts.createQualifiedName(
    ts.createIdentifier(typeFile.moduleName),
    ts.createIdentifier(typeKind)
    ),
    undefined
    ),
    ts.createLiteralTypeNode(ts.createStringLiteral(identifier))
    ),
    undefined)
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };

    View Slide

  69. 4. ts.PropertySignature
    プロパティシグネチャを生成
    ts.createPropertySignature(
    undefined,
    ts.createStringLiteral(`${typeFile.namespace}/${identifier}`),
    undefined,
    ts.createIndexedAccessTypeNode(
    ts.createTypeReferenceNode(
    ts.createQualifiedName(
    ts.createIdentifier(typeFile.moduleName),
    ts.createIdentifier(typeKind)
    ),
    undefined
    ),
    ts.createLiteralTypeNode(ts.createStringLiteral(identifier))
    ),
    undefined)
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };

    View Slide

  70. 5. ts.TypeAliasDeclaration
    生成したプロパティシグネチャ配列
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    ts.createTypeAliasDeclaration(
    undefined,
    undefined,
    ts.createIdentifier(identifier),
    undefined,
    ts.createTypeLiteralNode(typeElements)
    )

    View Slide

  71. 5. ts.TypeAliasDeclaration
    ts.createTypeLiteralNode
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    ts.createTypeAliasDeclaration(
    undefined,
    undefined,
    ts.createIdentifier(identifier),
    undefined,
    ts.createTypeLiteralNode(typeElements)
    )

    View Slide

  72. 5. ts.TypeAliasDeclaration
    ts.createIdentifier
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    ts.createTypeAliasDeclaration(
    undefined,
    undefined,
    ts.createIdentifier(identifier),
    undefined,
    ts.createTypeLiteralNode(typeElements)
    )

    View Slide

  73. 5. ts.TypeAliasDeclaration
    TypeAliasDeclaration を生成
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    ts.createTypeAliasDeclaration(
    undefined,
    undefined,
    ts.createIdentifier(identifier),
    undefined,
    ts.createTypeLiteralNode(typeElements)
    )

    View Slide

  74. 6. ts.NodeArray
    TypeAliasDeclaration
    の配列を生成
    const printer = ts.createPrinter()
    const emptyFile = ts.createSourceFile('', '', ts.ScriptTarget.ES2015)
    return printer.printList(
    ts.ListFormat.MultiLine,
    ts.createNodeArray([
    ...importModules(typeFiles),
    importByLiteral('vuex'),
    declareModule('vuex', [
    treeTypeAliasDeclaration(program, fileTree, 'RootState', 'S'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootGetters', 'G'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootMutations', 'M'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootActions', 'A')
    ])
    ]),
    emptyFile
    )

    View Slide

  75. 6. ts.NodeArray
    module declaration space
    を生成
    const printer = ts.createPrinter()
    const emptyFile = ts.createSourceFile('', '', ts.ScriptTarget.ES2015)
    return printer.printList(
    ts.ListFormat.MultiLine,
    ts.createNodeArray([
    ...importModules(typeFiles),
    importByLiteral('vuex'),
    declareModule('vuex', [
    treeTypeAliasDeclaration(program, fileTree, 'RootState', 'S'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootGetters', 'G'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootMutations', 'M'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootActions', 'A')
    ])
    ]),
    emptyFile
    )

    View Slide

  76. 6. ts.NodeArray
    import module 構文
    を生成
    const printer = ts.createPrinter()
    const emptyFile = ts.createSourceFile('', '', ts.ScriptTarget.ES2015)
    return printer.printList(
    ts.ListFormat.MultiLine,
    ts.createNodeArray([
    ...importModules(typeFiles),
    importByLiteral('vuex'),
    declareModule('vuex', [
    treeTypeAliasDeclaration(program, fileTree, 'RootState', 'S'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootGetters', 'G'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootMutations', 'M'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootActions', 'A')
    ])
    ]),
    emptyFile
    )

    View Slide

  77. 6. ts.NodeArray
    変換した AST が完成 const printer = ts.createPrinter()
    const emptyFile = ts.createSourceFile('', '', ts.ScriptTarget.ES2015)
    return printer.printList(
    ts.ListFormat.MultiLine,
    ts.createNodeArray([
    ...importModules(typeFiles),
    importByLiteral('vuex'),
    declareModule('vuex', [
    treeTypeAliasDeclaration(program, fileTree, 'RootState', 'S'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootGetters', 'G'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootMutations', 'M'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootActions', 'A')
    ])
    ]),
    emptyFile
    )

    View Slide

  78. 7. ts.Printer
    文字列に変換 const printer = ts.createPrinter()
    const emptyFile = ts.createSourceFile('', '', ts.ScriptTarget.ES2015)
    return printer.printList(
    ts.ListFormat.MultiLine,
    ts.createNodeArray([
    ...importModules(typeFiles),
    importByLiteral('vuex'),
    declareModule('vuex', [
    treeTypeAliasDeclaration(program, fileTree, 'RootState', 'S'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootGetters', 'G'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootMutations', 'M'),
    mapTypeAliasDeclaration(program, typeFiles, 'RootActions', 'A')
    ])
    ]),
    emptyFile
    )

    View Slide

  79. 8. fs.writeFileSync
    ファイルに書き込む import * as fs from 'fs'
    export default (dir: string, fileName: string, code: string) => {
    if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir)
    }
    fs.writeFileSync(`${dir}${fileName}.ts`, code)
    }

    View Slide

  80. import * as COUNTER from "〜/store/counter/type";
    import * as TODOS from "〜/store/todos/type";
    import "vuex";
    declare module "vuex" {
    type RootState = {
    counter: {
    count: COUNTER.S["count"];
    };
    todos: {
    todos: TODOS.S["todos"];
    };
    };
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo2": COUNTER.G["expo2"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    type RootMutations = {
    "counter/setCount": COUNTER.M["setCount"];
    "counter/multi": COUNTER.M["multi"];
    "counter/increment": COUNTER.M["increment"];
    "counter/decrement": COUNTER.M["decrement"];
    "todos/addTodo": TODOS.M["addTodo"];
    "todos/doneTodo": TODOS.M["doneTodo"];
    };
    type RootActions = {
    "counter/asyncSetCount": COUNTER.A["asyncSetCount"];
    "counter/asyncMulti": COUNTER.A["asyncMulti"];
    "counter/asyncIncrement": COUNTER.A["asyncIncrement"];
    "counter/asyncDecrement": COUNTER.A["asyncDecrement"];
    "todos/asyncAddTodo": TODOS.A["asyncAddTodo"];
    "todos/asyncDoneTodo": TODOS.A["asyncDoneTodo"];
    };
    }
    自動で出力される
    メタ型定義
    import 'vuex'
    declare module 'vuex' {
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    type Mutations = { [K in keyof M]: (state: S, payload: M[K]) => void }
    type ExCommit = (type: T, payload?: M[T]) => void
    type ExDispatch = (type: T, payload?: A[T]) => any
    type ExActionContext = {
    commit: ExCommit
    dispatch: ExDispatch
    state: S
    getters: G
    rootState: RootState
    rootGetters: RootGetters
    }
    type Actions = {
    [K in keyof A]: (ctx: ExActionContext, payload: A[K]) => any
    }
    interface ExStore extends Store {
    getters: RootGetters
    commit: ExCommit
    dispatch: ExDispatch
    }
    type StoreContext = ExActionContext<
    RootState,
    RootActions,
    RootGetters,
    RootMutations
    >
    }

    View Slide

  81. import * as COUNTER from "〜/store/counter/type";
    import * as TODOS from "〜/store/todos/type";
    import "vuex";
    declare module "vuex" {
    type RootState = {
    counter: {
    count: COUNTER.S["count"];
    };
    todos: {
    todos: TODOS.S["todos"];
    };
    };
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo2": COUNTER.G["expo2"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    type RootMutations = {
    "counter/setCount": COUNTER.M["setCount"];
    "counter/multi": COUNTER.M["multi"];
    "counter/increment": COUNTER.M["increment"];
    "counter/decrement": COUNTER.M["decrement"];
    "todos/addTodo": TODOS.M["addTodo"];
    "todos/doneTodo": TODOS.M["doneTodo"];
    };
    type RootActions = {
    "counter/asyncSetCount": COUNTER.A["asyncSetCount"];
    "counter/asyncMulti": COUNTER.A["asyncMulti"];
    "counter/asyncIncrement": COUNTER.A["asyncIncrement"];
    "counter/asyncDecrement": COUNTER.A["asyncDecrement"];
    "todos/asyncAddTodo": TODOS.A["asyncAddTodo"];
    "todos/asyncDoneTodo": TODOS.A["asyncDoneTodo"];
    };
    }
    module declaration space
    に宣言しているため、
    自動生成された型は
    用意した型パズルに
    declaration merge される。
    import 'vuex'
    declare module 'vuex' {
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    type Mutations = { [K in keyof M]: (state: S, payload: M[K]) => void }
    type ExCommit = (type: T, payload?: M[T]) => void
    type ExDispatch = (type: T, payload?: A[T]) => any
    type ExActionContext = {
    commit: ExCommit
    dispatch: ExDispatch
    state: S
    getters: G
    rootState: RootState
    rootGetters: RootGetters
    }
    type Actions = {
    [K in keyof A]: (ctx: ExActionContext, payload: A[K]) => any
    }
    interface ExStore extends Store {
    getters: RootGetters
    commit: ExCommit
    dispatch: ExDispatch
    }
    type StoreContext = ExActionContext<
    RootState,
    RootActions,
    RootGetters,
    RootMutations
    >
    }

    View Slide

  82. 型推論に重要なのは、
    「Literal Types 」
    これを自由に創出できる
    Node.js x Compiler API。
    import * as COUNTER from "〜/store/counter/type";
    import * as TODOS from "〜/store/todos/type";
    import "vuex";
    declare module "vuex" {
    type RootState = {
    counter: {
    count: COUNTER.S["count"];
    };
    todos: {
    todos: TODOS.S["todos"];
    };
    };
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo2": COUNTER.G["expo2"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    type RootMutations = {
    "counter/setCount": COUNTER.M["setCount"];
    "counter/multi": COUNTER.M["multi"];
    "counter/increment": COUNTER.M["increment"];
    "counter/decrement": COUNTER.M["decrement"];
    "todos/addTodo": TODOS.M["addTodo"];
    "todos/doneTodo": TODOS.M["doneTodo"];
    };
    type RootActions = {
    "counter/asyncSetCount": COUNTER.A["asyncSetCount"];
    "counter/asyncMulti": COUNTER.A["asyncMulti"];
    "counter/asyncIncrement": COUNTER.A["asyncIncrement"];
    "counter/asyncDecrement": COUNTER.A["asyncDecrement"];
    "todos/asyncAddTodo": TODOS.A["asyncAddTodo"];
    "todos/asyncDoneTodo": TODOS.A["asyncDoneTodo"];
    };
    }
    import 'vuex'
    declare module 'vuex' {
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    type Mutations = { [K in keyof M]: (state: S, payload: M[K]) => void }
    type ExCommit = (type: T, payload?: M[T]) => void
    type ExDispatch = (type: T, payload?: A[T]) => any
    type ExActionContext = {
    commit: ExCommit
    dispatch: ExDispatch
    state: S
    getters: G
    rootState: RootState
    rootGetters: RootGetters
    }
    type Actions = {
    [K in keyof A]: (ctx: ExActionContext, payload: A[K]) => any
    }
    interface ExStore extends Store {
    getters: RootGetters
    commit: ExCommit
    dispatch: ExDispatch
    }
    type StoreContext = ExActionContext<
    RootState,
    RootActions,
    RootGetters,
    RootMutations
    >
    }

    View Slide

  83. import * as COUNTER from "〜/store/counter/type";
    import * as TODOS from "〜/store/todos/type";
    import "vuex";
    declare module "vuex" {
    type RootState = {
    counter: {
    count: COUNTER.S["count"];
    };
    todos: {
    todos: TODOS.S["todos"];
    };
    };
    type RootGetters = {
    "counter/double": COUNTER.G["double"];
    "counter/expo2": COUNTER.G["expo2"];
    "counter/expo": COUNTER.G["expo"];
    "todos/todosCount": TODOS.G["todosCount"];
    "todos/doneCount": TODOS.G["doneCount"];
    };
    type RootMutations = {
    "counter/setCount": COUNTER.M["setCount"];
    "counter/multi": COUNTER.M["multi"];
    "counter/increment": COUNTER.M["increment"];
    "counter/decrement": COUNTER.M["decrement"];
    "todos/addTodo": TODOS.M["addTodo"];
    "todos/doneTodo": TODOS.M["doneTodo"];
    };
    type RootActions = {
    "counter/asyncSetCount": COUNTER.A["asyncSetCount"];
    "counter/asyncMulti": COUNTER.A["asyncMulti"];
    "counter/asyncIncrement": COUNTER.A["asyncIncrement"];
    "counter/asyncDecrement": COUNTER.A["asyncDecrement"];
    "todos/asyncAddTodo": TODOS.A["asyncAddTodo"];
    "todos/asyncDoneTodo": TODOS.A["asyncDoneTodo"];
    };
    }
    Compiler API には、
    未解決の型課題を解く
    ポテンシャルがある。
    import 'vuex'
    declare module 'vuex' {
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    type Mutations = { [K in keyof M]: (state: S, payload: M[K]) => void }
    type ExCommit = (type: T, payload?: M[T]) => void
    type ExDispatch = (type: T, payload?: A[T]) => any
    type ExActionContext = {
    commit: ExCommit
    dispatch: ExDispatch
    state: S
    getters: G
    rootState: RootState
    rootGetters: RootGetters
    }
    type Actions = {
    [K in keyof A]: (ctx: ExActionContext, payload: A[K]) => any
    }
    interface ExStore extends Store {
    getters: RootGetters
    commit: ExCommit
    dispatch: ExDispatch
    }
    type StoreContext = ExActionContext<
    RootState,
    RootActions,
    RootGetters,
    RootMutations
    >
    }
    創出 X 導出

    View Slide

  84. おさらい

    View Slide

  85. Getters ✅ state, ✅ getters, ✅ rootState, ✅ rootGetters
    推論観点で現実的な「型パズル」を構築する
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters =
    Todos.RG & Counter.RG

    View Slide

  86. Getters ✅ state, ✅ getters, ✅ rootState, ✅ rootGetters
    Compiler API に委ね「メタ型定義」を創出する
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters =
    Todos.RG & Counter.RG

    View Slide

  87. Getters ✅ state, ✅ getters, ✅ rootState, ✅ rootGetters
    最後まで残ったメタ型定義…
    export const getters: Getters = {
    todosCount(state, getters, rootState, rootGetters) { ... },
    doneCount(state, getters, rootState, rootGetters) { ... }
    }
    type Getters = {
    [K in keyof G]: (
    state: S,
    getters: G,
    rootState: RootState,
    rootGetters: RootGetters
    ) => G[K]
    }
    export type S = { todos: Todo[] }
    export type G = {
    todosCount: number
    doneCount: number
    }
    export type RG = {
    'todos/todosCount': G['todosCount']
    'todos/doneCount': G['doneCount']
    }
    type RootState = {
    todos: Todos.S
    counter: Counter.S
    }
    type RootGetters =
    Todos.RG & Counter.RG

    View Slide

  88. AST 使うなら、それ不要では…?

    View Slide

  89. 誰もが「なんとなく」型を付与し
    推論がまわりきるゴールまであと少し…

    View Slide

  90. To Be Continued ...
    @ Vue Fes Japan 2019

    View Slide

  91. Appendix
    謝辞:DEMO 作成にあたり参考にさせて頂いた資料・ツール
    ■ talks.leko.jp: https://talks.leko.jp/
    ■ TypeScript Compiler API の基本的な使い方: https://katashin.info/2018/02/24/221
    ■ Using the Compiler API: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API
    ■ TypeScript AST Viewer: https://ts-ast-viewer.com/

    View Slide