Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for TypeScript config files #117

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Changes from 3 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5e5fc4c
feat: Add support for TypeScript config files
aryaemami59 Mar 9, 2024
50676fa
Update start date in `README.md`
aryaemami59 Mar 9, 2024
9ab30b5
Add RFC PR
aryaemami59 Mar 12, 2024
c0c0714
Link prior RFC and issue regarding support for `.eslintrc.ts` files.
aryaemami59 Mar 12, 2024
247393f
Add `External References` section
aryaemami59 Mar 12, 2024
d13d556
Add alternatives considered for parsing TypeScript configuration files
aryaemami59 Mar 12, 2024
5e68795
Add links to TypeScript documentation in `External References` section
aryaemami59 Mar 12, 2024
24dac1d
Add some basic examples
aryaemami59 Mar 12, 2024
3863834
Convert `Related Discussions` to a list.
aryaemami59 Mar 12, 2024
d5e16db
Inform about the possibility of type checking in `.js` config files
aryaemami59 Mar 12, 2024
0e09c10
Add question related to `defineConfig` to `Open Questions`
aryaemami59 Mar 17, 2024
3c37a94
Include Docusaurus in the list of frameworks that are using `jiti`
aryaemami59 Mar 17, 2024
e0da6c3
Add new open question about how the feature interacts with `--config`
aryaemami59 Mar 28, 2024
24b0893
Add answer for question about caching
aryaemami59 Mar 28, 2024
ef591fc
Add answer about interoperability question
aryaemami59 Mar 28, 2024
7dfea39
Add how and where we use `jiti` in the Detailed Design` section
aryaemami59 Mar 28, 2024
de7e87a
Add missing section related to `importedConfigFileModificationTime`
aryaemami59 Apr 2, 2024
fb612ed
Enable `esmResolve` for `jiti`
aryaemami59 Apr 2, 2024
f60a15c
Update `Detailed Design` section
aryaemami59 Apr 4, 2024
fef64ec
Add disclaimer about `jiti` not supporting the top-level `await` syntax
aryaemami59 Apr 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
81 changes: 79 additions & 2 deletions designs/2024-support-ts-config-files/README.md
Expand Up @@ -36,6 +36,83 @@ The goal is to seamlessly support TypeScript configuration files in ESLint. To a

So far the tool that seems to be the most suitable for this purpose is [`jiti`](https://www.npmjs.com/package/jiti). It does not introduce side effects and performs well, demonstrating its reliability. It also seems to be more battle-tested given some established frameworks such as [Nuxt](https://github.com/nuxt/nuxt), [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) and [Docusaurus](https://github.com/facebook/docusaurus) have been using it to load their configuration files.
aryaemami59 marked this conversation as resolved.
Show resolved Hide resolved

- Here is how we would use [`jiti`](https://www.npmjs.com/package/jiti) to load TypeScript configuration files:

inside [`lib/eslint/eslint.js`](https://github.com/eslint/eslint/blob/main/lib/eslint/eslint.js):

```js
/**
* Check if the file is a TypeScript file.
* @param {string} filePath The file path to check.
* @returns {boolean} `true` if the file is a TypeScript file, `false` if it's not.
*/
function isFileTS(filePath) {
const fileExtension = path.extname(filePath);

return fileExtension.endsWith("ts");
}

/**
* Load the config array from the given filename.
* @param {string} filePath The filename to load from.
* @returns {Promise<any>} The config loaded from the config file.
*/
async function loadFlatConfigFile(filePath) {
debug(`Loading config from ${filePath}`);

const fileURL = pathToFileURL(filePath);

debug(`Config file URL is ${fileURL}`);

const mtime = (await fs.stat(filePath)).mtime.getTime();

/*
* Append a query with the config file's modification time (`mtime`) in order
* to import the current version of the config file. Without the query, `import()` would
* cache the config file module by the pathname only, and then always return
* the same version (the one that was actual when the module was imported for the first time).
*
* This ensures that the config file module is loaded and executed again
* if it has been changed since the last time it was imported.
* If it hasn't been changed, `import()` will just return the cached version.
*
* Note that we should not overuse queries (e.g., by appending the current time
* to always reload the config file module) as that could cause memory leaks
* because entries are never removed from the import cache.
*/
fileURL.searchParams.append("mtime", mtime);

/*
* With queries, we can bypass the import cache. However, when import-ing a CJS module,
* Node.js uses the require infrastructure under the hood. That includes the require cache,
* which caches the config file module by its file path (queries have no effect).
* Therefore, we also need to clear the require cache before importing the config file module.
* In order to get the same behavior with ESM and CJS config files, in particular - to reload
* the config file only if it has been changed, we track file modification times and clear
* the require cache only if the file has been changed.
*/
if (importedConfigFileModificationTime.get(filePath) !== mtime) {
delete require.cache[filePath];
}

const isTS = isFileTS(filePath);

if (isTS) {
const jiti = (await import("jiti")).default(__filename, { interopDefault: true });

const config = jiti(fileURL.href);

return config;
}

const config = (await import(fileURL.href)).default;

importedConfigFileModificationTime.set(filePath, mtime);
aryaemami59 marked this conversation as resolved.
Show resolved Hide resolved

return config;
}
```

## Examples
aryaemami59 marked this conversation as resolved.
Show resolved Hide resolved

with `eslint.config.mts` file:
Expand Down Expand Up @@ -194,9 +271,9 @@ While developing this feature, we considered the following alternatives:
you can remove this section.
-->

1. How is caching going to work with TypeScript config files?
1. How is caching going to work with TypeScript config files? We only cache the computed result of loading a config file, so I don't think this should be a problem.
2. Should we look at the nearest `tsconfig.json` file to determine the module resolution for `eslint.config.ts` files? Most likely not, but it's worth considering.
aryaemami59 marked this conversation as resolved.
Show resolved Hide resolved
3. Should we allow some sort of interoperability between JavaScript and TypeScript configuration files? For example, should we allow a TypeScript configuration file to extend a JavaScript configuration file and vice versa?
3. Should we allow some sort of interoperability between JavaScript and TypeScript configuration files? For example, should we allow a TypeScript configuration file to extend a JavaScript configuration file and vice versa? I don't believe this is an issue as the `extends` key isn't supported. Users just use `import` to load anything else they need.
4. Should we allow `eslint.config.ts` to be able to use `export default` as well as `module.exports` (might be related to [TypeScript's automatic Module Detection](https://www.typescriptlang.org/tsconfig#moduleDetection))?
aryaemami59 marked this conversation as resolved.
Show resolved Hide resolved
5. Tools like [Vitest](https://github.com/vitest-dev/vitest) export a [`defineConfig`](https://vitest.dev/config/file.html#managing-vitest-config-file) function to make it easier to write configuration files in TypeScript. Should we consider doing something similar for ESLint?
aryaemami59 marked this conversation as resolved.
Show resolved Hide resolved
6. How does the feature interact with the [CLI option](https://eslint.org/docs/latest/use/command-line-interface#options) [`--config`](https://eslint.org/docs/latest/use/command-line-interface#-c---config) for specifying a config file? It doesn't behave any differently, same as before. You can do `eslint . --config=eslint.config.ts` or `eslint . -c eslint.config.ts` and they just work. Same as with a `eslint.config.js` file.
Expand Down