microsoft / microsoft/TypeScript

Design Meeting Notes, 2026-07-23

Open
#63,676 0 comments 2 reactions 0 assignees View on GitHub
Design Notes
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

# 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:

```ts
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

```json5
// tsconfig.json
{
"compilerOptions": {
// ...
},
"contentMappers": [
{
"package": "vue-content-mapper",
"extensions": [".vue"],
}
],
"include": ["src"]
}
```

```json5
// 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:
```vue

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++;
}



{{ count }} {{ label }}


doubled = {{ doubled }}


+1

  • {{ n }}


high


```
* Kinda remaps to...
```ts
// 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 `` 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.

Contributor guide

Open the contributing guide

Research direction

This is a design-meeting record rather than an implementation task, and it names no source files or tests. Start by reading the linked pull requests 4699 and 4712 and the referenced TypeScript issue comment. Done would require an agreed JavaScript emit API and content-mapper protocol scope before implementation work can be defined.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
compilers, devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.