microsoft / microsoft/typespec

Proposal: JavaScript/TypeScript configuration files (tsp.config.js / tsp.config.ts) for explicit library resolution

Open
#9,057 7 comments 1 reaction 0 assignees View on GitHub
compiler:core design:needed triaged:core
Dominant language
Java
Stars
5.9k
Forks
394
Avg merge
1d 23h
Merged PRs (30d)
104

Description

### Clear and concise description of the problem

# Intro
Currently, TypeSpec primarily relies on `tspconfig.yaml` for configuration. This YAML-based configuration requires TypeSpec to resolve custom linter libraries and dependencies based on string names internally.

This internal resolution process implicitly depends on the existence of a traditional node_modules structure. This creates significant friction and requires workarounds in modern JavaScript runtimes like Deno, which do not create node_modules directories by default.

For example, to use a custom linter in a Deno environment, developers must currently create an explicit workaround involving symbolic links and dummy package.json files within an emulated node_modules structure to satisfy TypeSpec's internal library resolution logic.

Therefore, I propose adding support for JavaScript (`tsp.config.js`) and TypeScript (`tsp.config.ts`) configuration files.

This approach, similar to the modern ["Flat Config" adopted by ESLint](https://eslint.org/docs/latest/use/configure/configuration-files), allows the configuration to be defined as a JavaScript object, enabling the use of standard ESM import statements.

By using import, we delegate library and module resolution to the host JavaScript runtime. This eliminates TypeSpec's need to parse strings and implicitly rely on file system conventions like node_modules.

## Example

```ts
// tsp.config.ts
import { defineConfig } from "@typespec/compiler";
import bestPractices from "@typespec/best-practices/recommended";
import myCoolLinterRule from "./linter.ts";
import openapi3Emitter from "@typespec/openapi3";

export default defineConfig({
emit: [
openapi3Emitter,
],

options: {
"@typespec/openapi3": {
"emitter-output-dir": "{output-dir}",
"output-file": "{service-name}.{version}.spec.yaml",
},
},

linter: {
extends: [
bestPractices,
myCoolLinterRule
],
},
});

// linter.ts
import { defineLinter } from "@typespec/compiler";
import { requiredDocRule } from "./rules/required-doc.rule.ts";

export const $linter = defineLinter({
...
});
```

in YAML:
```yaml
emit:
- "@typespec/openapi3"

options:
"@typespec/openapi3":
emitter-output-dir: "{output-dir}"
output-file: "{service-name}.{version}.spec.yaml"

linter:
library:
- "@typespec/best-practices/recommended"
```

## Benefits and Rationale

True Runtime Agnosticism: By delegating resolution to the host runtime (via import), TypeSpec achieves better compatibility with diverse environments without requiring file system hacks.

Type Safety for TypeScript: Importing a defineConfig helper allows users to write their configuration with full type safety, IntelliSense, and compilation checks. This is especially useful now that runtimes like Node.js 24+ can execute TypeScript files directly.

Simplified Parser: This change reduces the complexity within TypeSpec's compiler, as the need for a custom YAML parser and string-based module resolution logic is diminished or removed for the JS/TS config path.

Dynamic Configuration: Users gain the ability to use standard JavaScript logic (e.g., conditional statements, environment variables) to dynamically generate their configurations.

### Checklist

- [x] Follow our [Code of Conduct](https://github.com/microsoft/typespec/blob/main/CODE_OF_CONDUCT.md)
- [x] Read the [docs](https://typespec.io/docs/).
- [x] Check that there isn't already an issue that request the same feature to avoid creating a duplicate.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.