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

[FR]: Better documentation for tsconfig #740

Open
antspy opened this issue Nov 28, 2024 · 7 comments
Open

[FR]: Better documentation for tsconfig #740

antspy opened this issue Nov 28, 2024 · 7 comments
Labels
enhancement New feature or request

Comments

@antspy
Copy link

antspy commented Nov 28, 2024

What is the current behavior?

Hi,

the current documentation for tsconfig (https://github.com/aspect-build/rules_ts/blob/main/docs/tsconfig.md) it's a bit unclear. It is recommended to use a single tsconfig file at the root of the repository and use it for every ts_project target, but it doesn't explain how to deal with different target types.

Tsconfig has some options (e.g. moduleResolution, module) which depend on the type of output we're building (a node binary requires different options vs a nextJS server). So it's unclear how a single tsconfig can work for both.

Even if one can extend the ts_project when needed (e.g. overwrite the relevant tsconfig options in the nextJS target), it's unclear how one would deal with base libraries which are used both in a node binary and a nextJS server. What should their tsconfig be set to, if it should apply to both cases?

Describe the feature

Would be great if this could be clarified and what is the expected way to structure the ts_project / ts_config targets in these cases.
Ideally a library is just a library (like py_library) and doesn't require potentially incompatible options to be set based on where it will be used downstream

@antspy antspy added the enhancement New feature or request label Nov 28, 2024
@jbedard
Copy link
Member

jbedard commented Nov 29, 2024

I think we are just missing one sentence explaining when+how to extend that root tsconfig.

I don't understand the issue with libraries used in both node and nextjs. How is that relevant to tsconfig?

@antspy
Copy link
Author

antspy commented Nov 30, 2024

I think we are just missing one sentence explaining when+how to extend that root tsconfig.

Yes, that would be great! :)

I don't understand the issue with libraries used in both node and nextjs. How is that relevant to tsconfig?

Well, each ts file is associated with a tsconfig during a build. The tsconfig has options (like moduleResolution, module which need to be different in different cases (e.g. nodenext when ran on node, bundler when ran as a nextJS target).

So if I have a lib.ts file which is used by both a node binary and a nextJS app, like so:

nodebin/
  main.js   // <--- Uses the tsconfig with `nodenext` configured 
frontend/
  page.tsx  // <----- Uses the tsconfig with `bundler` configured
lib/
  lib.ts // <------ Which tsconfig to use?

I don't know which tsconfig to use in lib, because it depends on whether we are compiling for main.js or for page.tsx. Does this make sense?

@jbedard
Copy link
Member

jbedard commented Dec 1, 2024

So you're compiling the same .ts files multiple times with different configs?

Ideally you wouldn't have to do that at all... compile once to a format that can be used by a bundler and node. Otherwise it's a mess that requires re-compiling the same files multiple times to multiple output locations etc :/

@antspy
Copy link
Author

antspy commented Dec 2, 2024

Ideally you wouldn't have to do that at all... compile once to a format that can be used by a bundler and node. Otherwise it's a mess that requires re-compiling the same files multiple times to multiple output locations etc :/

But how would I do this? The ts_project for the library inside the BUILD file requires a tsconfig, and tsconfig requires the nodenext vs bundler option - right? So how would one set this correctly for a library, if I don't know if it will be used in a nextJS (bundler) or node binary (nodenext)?

Example:

 ts_project(
        name = "lib",
        srcs = ["lib.ts"],
        visibility = ["//visibility:public"],
        deps = [
            "...",
        ],
        allow_js = True,
        declaration = True,
        incremental = True,
        preserve_jsx = True,
        resolve_json_module = True,
        tsconfig = "//:tsconfig",  // <---- Here!
        transpiler = "tsc",
        **kwargs
    )

with the root tsconfig being something like

{
  "compilerOptions": {
    "target": "es6",
    "lib": ["dom", "dom.iterable", "esnext"],
    "....",
    "module": "esnext",  // <--- here
    "moduleResolution": "bundler", // <---- here 
    }
}

Maybe it's something I am not understanding, but how do people normally deal with this?
Or should I remove non-common options from the 'root' tsconfig and add them manually on the final outputs?

Thanks a lot for your answers anyway! That's very much appreciated :)

@jbedard
Copy link
Member

jbedard commented Dec 5, 2024

I've actually never used the bundler option 🤔. I've always just compiled to esm and bundlers seem to handle that fine? The issue is normally old versions of node not handling esm, in which case I've occasionally bundled the node code as well - so bundling is what is done for browser vs node and they can share the same pre-compiled+cached ts outputs.

@antspy
Copy link
Author

antspy commented Dec 5, 2024

Do you have an example tsconfig I could take a look at?

Part of the problem is imports (which is how I arrived at this issue in the first place).

When running as a node binary, you should import the library as follows:

import MyClass from "../../../lib.js";

Note the .js suffix. Without it, node is not able to find the file correctly.

When running in nextJS, the opposite is true - one should use

import MyClass from "../../../lib";

in order to find it correctly. See https://www.typescriptlang.org/tsconfig/#moduleResolution, specifically "but unlike the Node.js resolution modes, bundler never requires file extensions on relative paths in imports."

So my current situation is:

  1. I have a library in typescript that I want to use in two different places.
  2. The import style depends on the moduleResolution option in tsconfig, which needs to be set two different values depending on what we're importing that library from (node or nextJS).
  3. VSCode can be customized to add the file extension or not, but it doesn't seem possible to make it understand when the file extension should be added and when not (i.e. either it always adds it or it never does).

So I am wondering what are the solutions that other people employ in their monorepo. Maybe there's a simple solution to this that I am completely missing :/

@jbedard
Copy link
Member

jbedard commented Dec 6, 2024

I've always seen the opposite lol - bundlers normally don't care about the file extension and can figure it out. It's not runtime so it's not super import if it's .mjs vs .cjs vs .js, the bundler just parses it and bundles it.

Don't the typescript docs confirm exactly that? When it says "but unlike the Node.js resolution modes, bundler never requires file extensions on relative paths in imports." I interpret that as a bundler not caring about extensions on relative paths but runtime might. So if you need to be compatible with both you'd use extensions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants