microsoft / microsoft/TypeScript
Design Meeting Notes, 2026-07-23
还没有人认领这个 Issue。
- 主要语言
- Go
- 星标
- 111k
- 派生
- 14.3k
- 平均合并
- 2 天 4 小时
- 30 天内合并 PR
- 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 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
这是一份设计会议记录,而不是实现任务,其中没有列出源文件或测试。请先阅读链接的 pull request 4699 和 4712,以及所引用的 TypeScript issue 评论。要视为完成,必须先就 JavaScript emit API 和 content-mapper 协议的范围达成一致,然后才能确定实现工作。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- javascript, typescript
- 领域
- compilers, devtools
- Issue 类型
- 功能
- 难度
- 5/5
- 预计耗时
- 一周以上
- 活跃度
- 冷清
- 描述清晰度
- 需要澄清
- 新手友好度
- 25/100