microsoft / microsoft/TypeScript
Design Meeting Notes, 2026-07-23
まだ誰も着手していません。
- 主要言語
- Go
- スター
- 111k
- フォーク
- 14.3k
- 平均マージ
- 2日 4時間
- マージ済み PR(30日)
- 132
説明
JavaScript Emit API
https://github.com/microsoft/typescript-go/pull/4699
- How would something like "compile on save" be reimplemented?
- Idea: LSP has
didSaveevent, clients can listen and trigger a custom emit command - Does emit in LSP make sense?
- We do expect it for the playground.
- Sounds like yes.
- Idea: LSP has
- Source maps?
- Should add those too.
- What's the (internal) API look like?
-
program.emit(emitOnly?: EmitOnly)- file system (including VFS if supplied), whole program, respects emit-blocking options likenoEmit,noEmitOnError, ...- Some weirdness in interaction, e.g.
emitDeclarationOnly
- Some weirdness in interaction, e.g.
-
program.emitToString(emitOnly?: EmitOnly)- in-memory string results, whole program, respects emit-blocking options- Basically a string over IPC
-
program.getJavaScriptEmit(files?: readonly DocumentIdentifier[])- in-memory string results, specific file selection, bypasses emit-blocking options- In-memory, does not respect
noEmit,emitDeclarationOnly, etc.
- In-memory, does not respect
-
program.getDeclarationEmit(files?: readonly DocumentIdentifier[])- (same) -
And EmitOnly is:
export enum EmitOnly { All = 0, OnlyJs = 1, OnlyDts = 2, }
-
- May have to consider how pre/post transformations work here, plus dynamically contributed content - recall that Angular might have been looking for this with ngc?
Content Mappers
- https://github.com/microsoft/TypeScript/issues/63800#issuecomment-4919145963
- https://github.com/microsoft/typescript-go/pull/4712
// tsconfig.json
{
"compilerOptions": {
// ...
},
"contentMappers": [
{
"package": "vue-content-mapper",
"extensions": [".vue"],
}
],
"include": ["src"]
}
// node_modules/vue-content-mapper/package.json
{
"name": "vue-content-mapper",
"version": "1.0.0",
"tsContentMapper": {
"exec": ["node", "dist/server.js"],
"compilerOptions": ["module", "jsx", "jsxImportSource"]
}
}
- What kind of versioning contract can we provide here?
- Stuff like LSP/Language Server Protocol doesn't provide a version per se - it's all capabilities based. Looks like same with AHP/Agent Host Protocol.
- Can keep version, but stick close to that model.
- Stuff like LSP/Language Server Protocol doesn't provide a version per se - it's all capabilities based. Looks like same with AHP/Agent Host Protocol.
- Why does this have to be a protocol instead of something like a potentially long-running process? Feels heavy.
- Don't want to have multiple invocations of the mapper.
- Cascading resolution: we don't always know which files the program itself includes (you can end up with
.vuefiles outside of the program) - Also, we have long-running things like
--watchand LSP where the program state will change over time. - These transforms are all supposed to be idempotent.
- One issue is: are these able to react to their configurations, and invalidate their resolution?
- We can react to the package itself changing, but not currently to configuration.
- Maybe we need something about config files that the plugin can watch.
- Do we allow this only for
--moduleResolution bundleror for--moduleResolution nodenextand others?- It works across everything.
- Top-level
contentMappersfield - how does this work with tsconfig inheritance?- It currently doesn't.
- This does have some invasiveness in our LSP implementation.
- Two different spans in a
.tsfile can map to the same span in a.vuefile.- Also, want these operations to coalesce occasionally.
- So operations at Line X Column Y now potentially have multiple results!
- So some operations default to the first result (hover), some concatenate results (go-to-definition).
- Two different spans in a
- This doesn't completely subsume Volar - Volar can mask/fix up errors.
- But you can build an LSP over this to do this if desired.
- What are examples of that funky LSP behavior?
- Take for example:
<script setup lang="ts"> import { ref, computed } from "vue"; const count = ref(0); const label = ref("clicks"); const label = computed(() => count.value === 1 ? "click" : "clicks"); const doubled = computed(() => count.value *2); function increment(): void { count.value++; } </script> <template> <div class="counter"> <p>{{ count }} {{ label }}</p> <p>doubled = {{ doubled }}</p> <button @click="increment()">+1</button> <ul> <li v-for="(n, i) in [count, doubled]" :key="i">{{ n }}</li> </ul> <span v-if="count > 5">high</span> </div> </template> - Kinda remaps to...
// SCRIPT SETUP START: export default createComponent({ // ... setup(props) { // VERBATIM-ISH SCRIPT BLOCK: const count = ref(0); // ... // GENERATED AFTER SCRIPT: return { count, }; }); // GENERATED FROM TEMPLATE BLOCK: function render(props) const { count } = props; // usage of count count; } - A reference to
countin the<template>block has a definition that doesn't exist in the original.vuesource, but also has a mapping to have a mapping back to a node that is not the "true" definition site.
- Take for example:
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
これは実装タスクではなく設計会議の記録であり、ソースファイルやテストは記載されていません。まず、リンクされている pull request 4699 と 4712、および参照されている TypeScript の issue コメントを読んでください。完了とするには、実装作業を定義する前に、合意された JavaScript emit API と content-mapper プロトコルのスコープが必要です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- javascript, typescript
- 領域
- compilers, devtools
- issue の種類
- 機能追加
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 静か
- 明瞭さ
- 説明が足りない
- 初心者へのやさしさ
- 25/100