microsoft / microsoft/TypeScript

Design Meeting Notes, 2026-07-23

未关闭
#63,676 0 条评论 2 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

Design Notes
主要语言
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 didSave event, 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.
  • 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 like noEmit, noEmitOnError, ...

      • Some weirdness in interaction, e.g. emitDeclarationOnly
    • 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.
    • 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

// 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.
  • 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 .vue files outside of the program)
    • Also, we have long-running things like --watch and 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 bundler or for --moduleResolution nodenext and others?
    • It works across everything.
  • Top-level contentMappers field - 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 .ts file can map to the same span in a .vue file.
      • 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).
  • 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 count in the <template> block has a definition that doesn't exist in the original .vue source, but also has a mapping to have a mapping back to a node that is not the "true" definition site.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

这是一份设计会议记录,而不是实现任务,其中没有列出源文件或测试。请先阅读链接的 pull request 4699 和 4712,以及所引用的 TypeScript issue 评论。要视为完成,必须先就 JavaScript emit API 和 content-mapper 协议的范围达成一致,然后才能确定实现工作。

由索引模型根据 Issue 内容生成。

评估

技术栈
javascript, typescript
领域
compilers, devtools
Issue 类型
功能
难度
5/5
预计耗时
一周以上
活跃度
冷清
描述清晰度
需要澄清
新手友好度
25/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。