Skip to content

Allow configuration of NodeBuilderFlags.AllowNodeModulesRelativePaths for un-distributed mono repos #37960

Description

@joeljeske

Search Terms

Suggestion

Allow the optional use of AllowNodeModulesRelativePaths during declaration creation

Use Cases

I understand why Wesley Wigham (@weswigham) added #27340 and it is correct for most consumers, especially those that publish packages for consumption on NPM.

However, it seems like for npm style mono-repos where packages are only consumed locally and not published to a registry, it is valid to have seemingly non-portable TS references (lerna, pnpm, rush, yarn workspaces). A relative path to a node_module may still be valid as long as the structure stays consistent, which it does for this style of mono repo with a single node_modules directory.

I am using a mono repo managed by Bazel and rules_nodejs and users are experiencing issues with the creation of declaration files that otherwise would contain relative paths to node_module files. bazel-contrib/rules_nodejs#1013

(The conversation in #30858 was helpful, however, it seemed to still be focusing on resolving portable types, which is only necessary when distributing the declaration files. If they are consumed locally, portability is not a concern.

Examples

I'm not sure the best way (or standard way) for these flags to be set, however, I imagine an entry in the TSConfig Compiler Options would make the most sense:

interface CompilerOptions {
  ...
+  allowUnportableDeclarationImports: true
} 

Currently I am patching Typescript to toggle on this behavior and it appears it is working just as expected.

typescript+3.7.3.patch.txt

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Activity

  1. RyanCavanaugh commented on Apr 15, 2020

    @RyanCavanaugh
    Member

    Wesley Wigham (@weswigham) I'm interested to hear your thoughts on this

  2. weswigham commented on Apr 15, 2020

    @weswigham
    Member

    Joel Jeske (@joeljeske) you only want this for incremental builds, right? Since if you trigger this error there's no way you're shipping the resulting .d.ts file as a redistributable.

  3. joeljeske commented on Apr 15, 2020

    @joeljeske
    Author

    Well no actually. I am using bazel with rules nodejs, but let me describe how it works. The build consists of many packages that link to each other via tsconfig path mappings (not via symlinking into node modules). Each package is built independently and only sees the type declarations of other local dependencies. The local dependencies all share a common top level node modules directory. In this way, if a package generates a declaration file with a relative import path into the node modules dir then downstream local packages that depend on that declaration file would still be able to resolve that import.

    None of the declarations files are ever used outside the mono repo

    Does that make much sense?

  4. weswigham commented on Apr 16, 2020

    @weswigham
    Member

    Sure; but what are the declaration files being built for? It's certainly not for publishing for consumption if you're OK with paths like this (since there's absolutely no way these paths could ever be shipped outside a specialized environment), so the only other use would be incremental style (similar to --incremental or --build mode) builds. As far as I know, bazel's default mode of operation, too, is incremental in nature.

  5. joeljeske commented on Apr 16, 2020

    @joeljeske
    Author

    The declaration files are used for type checking against other local packages. They never leave the repo.

    Example: local module A depends on local module B. The generated declaration files of B are used during the compilation of A.

    Yes bazel is incremental, but not in the sense of using —incremental as far as I know. It is incremental in the sense that module A will only be recompiled if the declaration files (public api) of module B changes.

    My mono repo has many small Packages in typescript that are used in a variety of places. The end product of the mono repo is a set of compiled JS applications ready for runtime. the d.ts files are only used in the intermediary build process as described.

  6. weswigham commented on Apr 16, 2020

    @weswigham
    Member

    The declaration files are used for type checking against other local packages. They never leave the repo.

    Right - pretty much the definition of an incremental build. I can see removing the error when incremental is set but declaration is not expressly set, however directly exposing an internal like this is not something I'd be comfortable with.

  7. joeljeske commented on Apr 17, 2020

    @joeljeske
    Author

    Yea I guess i was thinking about an incremental build from the standpoint of a single package. In my case each package is fully rebuilt at a time, not using the incremental TS option. But looking from the standpoint of the entire repo, yes it is incremental; some packages will have TSC invoked on them and some will not.

    Regardless of the terminology, I can say that I am not using TS incremental option and I am explicitly requesting the declaration files. I do respect concern of exposing implementation details, however, I feel like I have a valid use case here that does not fit into your limited exception:

    when incremental is set but declaration is not expressly set

  8. flolu commented on Apr 28, 2020

    @flolu

    I definitely agree, that Joel Jeske (@joeljeske) has a valid use case here.

    Especially, I do not think it would hurt anybody if it is disabled by default and can be enabled in the compiler options when needed.

  9. joeljeske commented on Apr 28, 2020

    @joeljeske
    Author

    Wesley Wigham (@weswigham) Ryan Cavanaugh (@RyanCavanaugh), do y'all have any additional thoughts regarding my use case? Would any supporting examples or additional use case explanations be of help?

  10. 15 remaining items

  11. ccstr commented on Oct 6, 2021

    @ccstr

    My use case is different than Joel Jeske (@joeljeske) that I am not making a bazel monorepo but a monorepo that is based on project references.

    I have a project P1 that exports a type T1 that derived from P1's node_modules type T2. P2 is another project that references P1 in the same monorepo using tsconfig's references. When P2 uses T1, I got:

    The inferred type of cannot be named without a reference to 'P1/node_modules/<some_package>/lib' this is likely not portable.

    Andrew Branch (@andrewbranch) looked into this case and pointed out that if I re-export T2 from P1 then the error will go away, I will be OK for the time being to re-export T2 but would rather not to if not required.

    I think there's couple of things could be improved here:

    • The error message is a bit misleading in that it seems to demand a direct reference from P2 to <some_package> but in-fact only need a re-export from P1 of type T2
    • In a referenced project, we don't really need to check for portability because the referenced project is not going to be shipped as a npm package. The d.ts files are just for incremental compilations purpose.

    Personally, I would be more than happy to inform TS in my tsconfig that I am not going to ship current project as a npm package in order for TS to safely skip some checks in exchange of flexibility or performance. Or if TS infer that automatically from composite: true it would work for my case too and makes perfect senses for me.

  12. added
    Won't FixThe severity and priority of this issue do not warrant the time or complexity needed to fix it
    on Dec 11, 2023
  13. weswigham commented on Dec 11, 2023

    @weswigham
    Member

    Honestly, the ecosystem issues having a flag that allows this opens up aren't worth the hassle. A simple flag that allows this would be misused (guaranteed) - the correct fix, in every circumstance, is just to do as the error asks and explicitly type annotate the position the error is on. That both speeds up the compiler and keeps the declarations predictably portable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Fix AvailableA PR has been opened for this issueNeeds InvestigationThis issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestoneWon't FixThe severity and priority of this issue do not warrant the time or complexity needed to fix it

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions