|
| 1 | +--- |
| 2 | +authors: joshuakgoldberg |
| 3 | +description: How typescript-eslint's new "Project Service" makes typed linting easier to configure, especially for large projects. |
| 4 | +slug: project-service |
| 5 | +tags: [parser, parser options, project, project service, tsconfig] |
| 6 | +title: Typed Linting with Project Service |
| 7 | +--- |
| 8 | + |
| 9 | +import Tabs from '@theme/Tabs'; |
| 10 | +import TabItem from '@theme/TabItem'; |
| 11 | + |
| 12 | +["Typed linting"](/blog/typed-linting), or enabling ESLint rules to understand TypeScript types, is one of the best parts of typescript-eslint. |
| 13 | +It enables a slew of [more powerful lint rules](/rules/?=recommended-typeInformation) that check for nuanced bugs, best practice violations, and other code issues that can only be detected using type information. |
| 14 | + |
| 15 | +Typed linting hasn't always been straightforward to configure or performant at runtime. |
| 16 | +We've seen users have to manage separate `tsconfig.eslint.json` files to enable typed linting — sometimes with different compiler options than the rest of the project. |
| 17 | +Not ideal. |
| 18 | + |
| 19 | +In typescript-eslint 8.0, we stabilized a **`parserOptions.projectService`** option that uses more powerful, streamlined TypeScript APIs than before. |
| 20 | +The "Project Service" brings several benefits: |
| 21 | + |
| 22 | +- ✍️ **Configuration**: simpler ESLint configs for typed linting with no ESLint-specific TSConfig files |
| 23 | +- 🧠 **Predictability**: uses the same type information services as editors, including more reliability |
| 24 | +- 🚀 **Scalability**: supporting TypeScript project references for larger repositories (i.e. monorepos) |
| 25 | + |
| 26 | +This blog post will cover how `parserOptions.projectService` simplifies configurations and aligns linting type information to what editors such as VS Code run with. |
| 27 | + |
| 28 | +:::tip |
| 29 | +See [Getting Started](/getting-started) to learn how to lint JavaScript and TypeScript code with typescript-eslint, then [Linting with Type Information](/getting-started/typed-linting) to onboard to typed linting. |
| 30 | +::: |
| 31 | + |
| 32 | +<!-- truncate --> |
| 33 | + |
| 34 | +## Introducing the Project Service |
| 35 | + |
| 36 | +Back in [Relative TSConfig Projects with `parserOptions.project = true` > Project Services](2023-09-18-parser-options-project-true.md#project-services), we'd mentioned a replacement for `parserOptions.project`: |
| 37 | + |
| 38 | +> The downside of having users specify `parserOptions.project` at all is that `@typescript-eslint/parser` needs manual logic to create TypeScript Programs and associate them with linted files. |
| 39 | +> Manual Program creation logic comes with a few issues: ... |
| 40 | +> |
| 41 | +> We're working on an option to instead call the same TypeScript "Project Service" APIs that editors such as VS Code use to create Programs for us instead. |
| 42 | +> Project Services will automatically detect the TSConfig for each file (like `project: true`), and will also allow type information to be computed for JavaScript files without the `allowJs` compiler option (unlike `project: true`). |
| 43 | +
|
| 44 | +Following a year of discussion and beta testing in typescript-eslint v6 and v7, we believe the new Project Service API is ready to be used by real-world projects. |
| 45 | +We therefore promoted the `parserOptions.EXPERIMENTAL_useProjectService` option to the stable name **`parserOptions.projectService`** in typescript-eslint v8. |
| 46 | + |
| 47 | +:::note |
| 48 | +See [Announcing typescript-eslint v8 > Project Service](/blog/announcing-typescript-eslint-v8#project-service) for the original announcement. |
| 49 | +::: |
| 50 | + |
| 51 | +## Configuration |
| 52 | + |
| 53 | +You can change over to the new Project Service API by replacing `project` with `projectService` in your ESLint configuration: |
| 54 | + |
| 55 | +<Tabs groupId="eslint-config"> |
| 56 | +<TabItem value="Flat Config"> |
| 57 | + |
| 58 | +```js title="eslint.config.js" |
| 59 | +export default tseslint.config({ |
| 60 | + // ... |
| 61 | + languageOptions: { |
| 62 | + parserOptions: { |
| 63 | + // Remove this line |
| 64 | + project: true, |
| 65 | + // Add this line |
| 66 | + projectService: true, |
| 67 | + tsconfigRootDir: import.meta.dirname, |
| 68 | + }, |
| 69 | + }, |
| 70 | + // ... |
| 71 | +}); |
| 72 | +``` |
| 73 | + |
| 74 | +</TabItem> |
| 75 | +<TabItem value="Legacy Config"> |
| 76 | + |
| 77 | +```js title=".eslintrc.cjs" |
| 78 | +module.exports = { |
| 79 | + // ... |
| 80 | + parser: '@typescript-eslint/parser', |
| 81 | + parserOptions: { |
| 82 | + // Remove this line |
| 83 | + project: true, |
| 84 | + // Add this line |
| 85 | + projectService: true, |
| 86 | + tsconfigRootDir: __dirname, |
| 87 | + }, |
| 88 | + // ... |
| 89 | +}; |
| 90 | +``` |
| 91 | + |
| 92 | +</TabItem> |
| 93 | +</Tabs> |
| 94 | + |
| 95 | +That's it! |
| 96 | + |
| 97 | +Other settings, including how you run ESLint and configure rules, should work the same. |
| 98 | + |
| 99 | +:::tip |
| 100 | +See [Packages > Parser > `projectService`](/packages/parser#projectservice) for more details on granular configuration options. |
| 101 | +::: |
| 102 | + |
| 103 | +### Additional Files |
| 104 | + |
| 105 | +One long-standing pain point of typed linting was enabling typed linting for files not included in the project's `tsconfig.json`. |
| 106 | +Common solutions in the traditional Program API were to either skip typed linting for those files or to create a `tsconfig.eslint.json` enabling the `allowJs` compiler option. |
| 107 | + |
| 108 | +The new Project Service API allows for a configuration object specifying `allowDefaultProject`: a glob of "out-of-project" files to lint with type information. |
| 109 | +That means you can lint those files without any new configuration files or TypeScript compiler options! |
| 110 | + |
| 111 | +For example, the following config solves the common case of projects that have root-level files like `eslint.config.js` or `vitest.config.ts`: |
| 112 | + |
| 113 | +<Tabs groupId="eslint-config"> |
| 114 | +<TabItem value="Flat Config"> |
| 115 | + |
| 116 | +```js title="eslint.config.js" |
| 117 | +export default tseslint.config({ |
| 118 | + // ... |
| 119 | + languageOptions: { |
| 120 | + parserOptions: { |
| 121 | + projectService: { |
| 122 | + allowDefaultProject: ['*.js'], |
| 123 | + }, |
| 124 | + tsconfigRootDir: import.meta.dirname, |
| 125 | + }, |
| 126 | + }, |
| 127 | + // ... |
| 128 | +}); |
| 129 | +``` |
| 130 | + |
| 131 | +</TabItem> |
| 132 | +<TabItem value="Legacy Config"> |
| 133 | + |
| 134 | +```js title=".eslintrc.cjs" |
| 135 | +module.exports = { |
| 136 | + // ... |
| 137 | + parser: '@typescript-eslint/parser', |
| 138 | + parserOptions: { |
| 139 | + projectService: { |
| 140 | + allowDefaultProject: ['*.js'], |
| 141 | + tsconfigRootDir: __dirname, |
| 142 | + }, |
| 143 | + }, |
| 144 | + // ... |
| 145 | +}; |
| 146 | +``` |
| 147 | + |
| 148 | +</TabItem> |
| 149 | +</Tabs> |
| 150 | + |
| 151 | +This means most projects should be able to remove all `tsconfig.eslint.json` files! |
| 152 | +🥳 |
| 153 | + |
| 154 | +:::tip |
| 155 | +See [Packages > Parser > `projectService` > `ProjectServiceOptions`](/packages/parser#projectserviceoptions) for more details on out-of-project files and other granular configuration options. |
| 156 | +::: |
| 157 | + |
| 158 | +## Predictability |
| 159 | + |
| 160 | +We've found configuring `tsconfig.eslint.json` files to be a common source of confusion with the traditional Program APIs. |
| 161 | +They can result in the type information used for _linting_ accidentally being different from the type information used for _type checking_. |
| 162 | +Unifying TypeScript configurations to the same `tsconfig.json` file(s) altogether avoids potential divergent types. |
| 163 | + |
| 164 | +Another benefit of using the new Project Service API is that typed linting requires no additional work in typescript-eslint for more difficult uses of ESLint and/or TypeScript. |
| 165 | +We sometimes had to de-optimize the traditional Program API to support use cases: |
| 166 | + |
| 167 | +- CLI `--fix` mode would lose type information after the first pass ([#9577](https://github.com/typescript-eslint/typescript-eslint/pull/9577)) |
| 168 | +- Extra file extensions such as `.svelte` and `.vue` were not supported at all ([#9504](https://github.com/typescript-eslint/typescript-eslint/issues/9504)) |
| 169 | + |
| 170 | +The new Project Service API does not suffer from these issues. |
| 171 | +It supports extra file extensions out-of-the-box and does not slow down when used with ESLint's `--fix`. |
| 172 | + |
| 173 | +## Scalability |
| 174 | + |
| 175 | +[TypeScript's project references](https://www.typescriptlang.org/docs/handbook/project-references.html) are how many larger projects, in particular monorepos, scale TypeScript type checking. |
| 176 | +They allow delineating discrete projects with their own `tsconfig.json` files and annotating which projects depend on which other projects. |
| 177 | +TypeScript is able to cache type information and only recheck projects that have changed in builds based on those project references. |
| 178 | + |
| 179 | +The traditional `parserOptions.project` API did not support project references for typed linting. |
| 180 | +We had experimented with adding support, but using the manual built-in TypeScript APIs would have been a significant maintenance investment with an unclear payoff. |
| 181 | + |
| 182 | +The new Project Service API does support project references out-of-the-box. |
| 183 | +This is a huge win for monorepos, as it means you can lint all of your projects with type information without needing to create a separate `tsconfig.eslint.json` file for each project. |
| 184 | + |
| 185 | +## Performance |
| 186 | + |
| 187 | +Supporting project references allows the new Project Service API to be significantly faster than the traditional `parserOptions.project` API in many monorepo cases. |
| 188 | +We've observed improvements for typed linting speed in real-world repositories [^babel-conversion] [^create-t3-app-conversion] [^sveltekit-conversion]. |
| 189 | +For smaller projects, the performance of the new Project Service API is similar to the traditional `parserOptions.project` API. |
| 190 | + |
| 191 | +When we first started working with the new project service API, it outperformed equivalent `parserOptions.project` setups by ~10-15%. |
| 192 | +Since then, we have observed regressions in performance that have brought it down in some cases to be slightly slower. |
| 193 | + |
| 194 | +We believe the new Project Service API should be faster than the traditional API, and are treating the lack of significant improvement as a bug. |
| 195 | +See [⚡ Performance: parserOptions.projectService sometimes no longer outperforms parserOptions.project](https://github.com/typescript-eslint/typescript-eslint/issues/9571) for more information. |
| 196 | + |
| 197 | +## Next Steps |
| 198 | + |
| 199 | +The new Project Service API is available in typescript-eslint v8.0.0 and later. |
| 200 | +We've been using it in our monorepo for over a year and have been thrilled to see many community repositories adopt it as well. |
| 201 | + |
| 202 | +### Giving Feedback |
| 203 | + |
| 204 | +We'd love to hear from you on how this option works for you. |
| 205 | +Does it live up to what we've promised, and/or does it have bugs we haven't fixed yet? |
| 206 | +Please do send us GitHub issues for any bugs you encounter or suggestions for how to improve the API. |
| 207 | + |
| 208 | +The [typescript-eslint Discord](https://discord.gg/FSxKq8Tdyg) is a great place to ask questions and engage with us more casually. |
| 209 | +For support in onboarding, feel free to ask in its `#help` channel. |
| 210 | +We'd be happy to help you try out `parserOptions.projectService` and learn more about how you use typescript-eslint. |
| 211 | + |
| 212 | +### Long Term Vision |
| 213 | + |
| 214 | +The new Project Service API is a great step towards making typed linting easier and more straightforward to configure. |
| 215 | +Our priority for the next year will be to improve the new Project Service API so that it works in all places the traditional Program API does. |
| 216 | +We won't remove the traditional project program behavior unless and until the new Project Service API is able to fully replace it. |
| 217 | + |
| 218 | +As of typescript-eslint@8.33.0, we've also extracted most of the Project Service code into a standalone [`@typescript-eslint/project-service`](/packages/project-service) package. |
| 219 | +It has no dependencies on ESLint and is designed to be usable for any linter to enable TypeScript's Project Service API for typed linting. |
| 220 | +See [Packages > Project Service](/packages/project-service) for more details. |
| 221 | + |
| 222 | +We're also looking forward to investigating support for [TypeScript's 10x faster Go port](https://github.com/typescript-eslint/typescript-eslint/issues/10940). |
| 223 | +Abstracting configuration details into the Project Service API means it will be much easier for typescript-eslint to support typed linting using "tsgo" without any additional user configuration. |
| 224 | + |
| 225 | +So, please, try out the new Project Service API. |
| 226 | +We're excited to hear how it works for you and what we can do to improve it. 💜 |
| 227 | + |
| 228 | +[^babel-conversion]: https://github.com/babel/babel/pull/16192#issue-2054613116 |
| 229 | + |
| 230 | +[^create-t3-app-conversion]: https://github.com/t3-oss/create-t3-app/pull/1936/#discussion_r1667389041 |
| 231 | + |
| 232 | +[^sveltekit-conversion]: https://github.com/sveltejs/kit/pull/13839 |
0 commit comments