vuejs / create-vue

🛠️ The recommended way to start a Vite-powered Vue project
Other
3.69k stars 421 forks source link

Document `tsconfig.*.json` #265

Open laterdayi opened 1 year ago

laterdayi commented 1 year ago

ref https://github.com/vuejs/tsconfig/issues/16

thanks @sodatea

bmulholland commented 9 months ago

Also, why does type-check use the vitest tsconfig? I'd expect it to use the app tsconfig.

haoqunjiang commented 9 months ago

Also, why does type-check use the vitest tsconfig? I'd expect it to use the app tsconfig.

Because the vitest tsconfig covers both the app modules and the test spec modules, therefore more comprehensive than the app tsconfig.

But never mind, after https://github.com/vuejs/create-vue/pull/274 we'll be using vue-tsc --build --force for type-checking. That would be more intuitive.

bmulholland commented 9 months ago

Got it, thanks @sodatea. I did try that out and ran into an issue, which I've flagged on the PR. Still, good to understand the current setup.

To throw in a tsconfig that threw me off more than others: the cypress one that's under cypress/e2e. I generated a scaffold and copied over the tsconfig files in the root dir to my actual project, but missed that one until just now. I only knew it existed because I was browsing around templates.

bmulholland commented 3 months ago

Okay, me again, back again. Really really struggling here, again, as I do every time I try to touch this. Please pardon some frustration here, I've been fighting with this all day. At this time, I'm pretty sure there's an actual bug in the resulting create-vue template, but have no way of even trying to understand the intent to confirm that. I can't even test different scenarios: I don't know what scenarios to test. I see something that somehow Volar is supposed to auto-select certain tsconfigs in some situations? How? What do I test? What about vue-tsc? How do all those pieces fit together?

I'm trying to follow the intent with the tsconfig.app.json and vitest and so forth and I'm just very lost. I put some of my confusion down here but honestly that's just the start. Do I need to configure tsserver or Volar somehow? If I need to add new types or libs, how do I do that? Do I need to set it in the app one and also in vitest? These questions are just the start. It feels like every time I try to fix something in this set of configs, I break something else, and then only find out ages later.

The biggest issue is that all of these thing have really large invisible footguns. tsconfig values get overwritten, and also it seems some of them are loaded by specific tools somehow, so it's easy to e.g. add one lib in one place and then suddenly you've broken type checking, but only for volar and for some editors, not when you run vue-tsc. Or several variations of that.

I'm also pretty sure there's at least one bug in the default configuration, as included in the link. But without being a tsconfig expert, I have no way of knowing if I'm just misunderstanding something, or misusing something, or what. I've fixed the "lib is overwritten" issue by putting the app tsconfig last, like https://github.com/vuejs/create-vue/issues/264 suggests. Does that break something? What? Is there something I need to test? That suggests I need to set reverseConfigFilePriority -- is that still a thing? Where? Do all devs need to set it? How?

I'm very frustrated and overwhelmed with all of it, TBH. I've tried my best to read about it but the discussion I can find talks about multiple options that have changed, none of which I understand. It seems like, to even try to add a single new "type" into my typescript project, I have to dig through all the history, multiple obscure tsconfig options, wrap my head around their interactions with Volar and vue-tsc, and more.

bmulholland commented 3 months ago

Key questions I need to understand:

  1. If I need to add types for my components, where do I add that?
  2. If I add it to somewhere that could be overwritten by another tsconfig, how do I manage that?
  3. If I want to add a lib, how do I do that?
  4. How am I supposed to configure my local dev server, to load vitest/app tsconfigs in the right place?
  5. What's the intent of the "include" blocks in each file? If I need to customize it for my project, what do I add where?
  6. How do I manage interactions between all the different tsconfigs?
  7. How can I see what the end result of config changes are? --showConfig just shows the list of references, not how those all compile together and overwrite each other
  8. Is it true that the vitest lib overwrites the vue tsconfig dom? Why or why not? Is that intentional?
  9. Why is the lib set to an empty array for vitest, anyway?
cexbrayat commented 3 months ago

Hi @bmulholland

Maybe you can add a few Stackblitz using https://stackblitz.com/github/vuejs/create-vue-templates/tree/main/typescript-vitest?file=src%2Fcomponents%2F__tests__%2FHelloWorld.spec.ts with your use-cases so we can take a look?

It's hard to answer without specific examples.

I'm not sure we'll be able to help with everything, but it's worth a try to see if someone in the community has the same issue. And we may be able to open specific issues on different repositories (create-vue may not be the right place for some things that I suspect are vue-tsc issues for example).

bmulholland commented 3 months ago

@cexbrayat Thanks for the offer! I'll do you one better: here's the documentation I wish I had, based on what I've been able to figure out. Hopefully someone (you?) can fact check & help with areas I'm unclear, and then we'll have the docs and can close out the issue :)

References I used:

create-vue tsconfig setup

create-vue sets up TypeScript using multiple tsconfig files. Here’s an overview of how these configurations work together:

Type checking

Type checking is done usingvue-tsc --build --force (see the “type-check” command in package.json). This approach leverages two important options:

  1. --build Option: This option enables TypeScript’s project references and incremental builds. By rechecking only the files that have changed since the last build, it speeds up repeat checks and improves overall efficiency.
  2. --force Option: The --force flag ensures that all files are type-checked, not just the ones that have changed. This is important because it provides a comprehensive check, ensuring that any potential issues, even those in unchanged files or due to changes in dependencies or configurations, are caught.

Since the build option is used, TS build info files are emitted. Each tsconfig sets their own output directory, within node_modules/.tmp, so it's always gitignored.

tsconfig files

Primary Configuration (tsconfig.json)

The main entry point for type checking is tsconfig.json. It includes references to two specific configurations:

Additional tsconfig.*.json files added

Approach

This tsconfig approach establishes TypeScript “sub-projects” using tsconfig references. This modular approach allows different parts of the project to have distinct TypeScript configurations.

Vitest

When using vitest, a tsconfig.vitest.json is generated, extending from tsconfig.app.json. The app configuration excludes test files (src/**/__tests__/*), while the test configuration overwrites that exclusion with "exclude": []. The result is that both tsconfig's match all app files, while the vitest config additionally matches test files. This requires careful consideration when modifying projects that use create-vue's tsconfig for vitest: see "Vitest Considerations" below.

This overlap in coverage ... TODO: why?. A more precise approach would be to have tsconfig.vitest.json match only test files, but TODO: why?

Only one tsconfig is actually used for files: vue-tsc and Volar. Both tools check the order of the main entry point tsconfig.json’s references, picking the first reference that matches a given file. This means that, while the sub-tsconfig files are isolated, the order in which the app and vitest tsconfigs are listed in the main file is important. TODO: is this true?.

Note that the default output from create-vue expects that Volar's reverseConfigFilePriority is disabled.

Isolation Between References

Typically with tsconfig usage, keys are overwritten within the extend chain. With references, however, they are isolated between different tsconfig references sections. Note that, as described above, tsconfig.vitest.json extends from tsconfig.app.json, so these two are not isolated.

Modifying the Configurations

These tsconfig files extend from other configurations, creating a chain. When adding new libraries or types, check the extended configuration files. Be aware that setting the same key in a configuration file will overwrite the previous value from the extended file.

For example, tsconfig.app.json extends from a tsconfig from Vue that includes DOM libs. If you need to add a new lib to tsconfig.app.json, you have to copy in the entire lib section from the base config, otherwise you will overwrite the lib value and lose all DOM types.

Vitest Considerations

When Vitest is used, modifying either tsconfig.app.json and/or tsconfig.vitest.json, there are interactions between the two to consider. The ones in vitest will overwrite the other.

For example, by default tsconfig.app.json has no "types" set, while tsconfig.vitest.json does. Since the vitest types will overwrite any set in tsconfig.app.json, if you need a type in both the app files and the vitest files, you'll have to duplicate those "types" entries in both files.

Type Checking with Volar and vue-tsc

Best Practices

When making changes, regularly verify which tsconfig file is being used for your files in the development environment: Use the VS Code status bar to check the active tsconfig and troubleshoot any configuration issues.

cexbrayat commented 2 months ago

@bmulholland Looks good! Thanks for taking the time to write this. I would also mention the @vue/tsconfig base project that the tsconfig files extend.

Anyway, this should probably be in the official vue.js docs; Would you mind opening a PR there to see if the docs team finds it interesting?

bmulholland commented 2 months ago

Awesome :)

I would also mention the @vue/tsconfig base project that the tsconfig files extend.

Yeah good idea, will add.

Would you mind opening a PR there to see if the docs team finds it interesting?

https://github.com/vuejs/docs/issues/2907 :)

haoqunjiang commented 2 months ago

Thanks for writing this up! It looks good.

Regarding the confusing setup of tsconfig.vitest.json, it was initially included for more comprehensive type-checking by default. However, considering the double-checking issue, the constantly re-appearing priority issue (such as https://github.com/vuejs/language-tools/issues/3951), I'm now considering excluding it from the root tsconfig.json by defaul. Instead, we can provide a dedicated command to run vue-tsc on the spec files. I haven't tried this approach myself so not sure if it's feasible. But this might save some documentation effort.

func0der commented 2 months ago

In lack of a better issue and since it is already discussed here: Having WebWorker code in my application and using FileReaderSync in the worker.ts file, lead to a lot of confusion (on my end) and false error messages, when I only added WebWorker to the tsconfig.app.json:

"compilerOptions: {
    ...
    "lib": [
      // Taken from "@vue/tsconfig/tsconfig.dom.json"
      "ES2020",
      "DOM",
      "DOM.Iterable",

      // Project specific
      "WebWorker"
    ]
}

As the tsconfig.vitest.json does come in last and sets the compilerOptions.libs to an empty array, TypeScript just goes: Yeah...let's include all esXXXX and dom related stuff (dunno where it is coming from, but you can see that is is there in node_modules/.tmp/tsconfig.vitest.tsbuildinfo), but drop everything else. Arrays are not merged in tsconfig.json files, so we are losing all information from tsconfig.app.json that is array related.

Two solutions. You could either defined the same compilerOptions.libs in tsconfig.vitest.json as you have in tsconfig.app.json or delete compilerOptions.libs from tsconfig.vitest.json entirely. Both of which defeat the purpose of it being defined as an empty array in there in the first place.

Maybe that is worth considering in this scenario.