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

Add a new compiler option moduleSuffixes to expand the node module resolver's search algorithm #48189

Merged
merged 8 commits into from
Mar 30, 2022

Conversation

afoxman
Copy link
Contributor

@afoxman afoxman commented Mar 9, 2022

This PR expands the node module resolver's search algorithm to include a list of module suffixes:

// react-native use-case: import './foo' -> search for foo.ios.ts, then foo.native.ts, and finally foo.ts
{
    "compilerOptions": {
        // empty string --> match without an extension
        "moduleSuffixes": [".ios", ".native", ""]
    }
}

// alternative use-case: import 'foo' -> search for foo-primary.ts, then foo__alt.ts (stop there)
{
    "compilerOptions": {
        "moduleSuffixes": ["-primary", "__alt"]
    }
}

This impacts all TypeScript workflows, including build and parsing for IntelliSense.

Developers who take advantage of this would have one tsconfig file per moduleSuffix list. The config file name should not be constrained to any particular pattern. Many 3rd-party tools already support specifying a tsconfig file name, explicitly, which makes this approach a good choice.

IntelliSense will need additional support to make this work. With multiple tsconfigs in play, IntelliSense will need to be told which one to use, or it will default to loading tsconfig.json. For the react-native use-case, I'm imagining this will be an IDE extension that exposes a platform selector in its config and/or in the UI. The corresponding tsconfig for that platform is passed to the IntelliSense tsserver process.

Fixes #21926

…reserve "falsy" values such as empty strings. Falsy values are normally stripped out.
@typescript-bot
Copy link
Collaborator

The TypeScript team hasn't accepted the linked issue #21926. If you can get it accepted, this PR will have a better chance of being reviewed.

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Mar 9, 2022
@sandersn sandersn requested review from andrewbranch and sheetalkamat and removed request for andrewbranch March 17, 2022 16:54
@sandersn sandersn added this to Not started in PR Backlog via automation Mar 17, 2022
@sandersn sandersn moved this from Not started to Waiting on reviewers in PR Backlog Mar 17, 2022
PR Backlog automation moved this from Waiting on reviewers to Waiting on author Mar 17, 2022
@andrewbranch
Copy link
Member

Oops, some of my suggestions are redundant with @sheetalkamat’s; her review came in while I was still browsing 😄

src/compiler/moduleNameResolver.ts Outdated Show resolved Hide resolved
src/compiler/moduleNameResolver.ts Outdated Show resolved Hide resolved
src/compiler/moduleNameResolver.ts Outdated Show resolved Hide resolved
src/compiler/types.ts Show resolved Hide resolved
src/compiler/types.ts Show resolved Hide resolved
src/compiler/commandLineParser.ts Outdated Show resolved Hide resolved
@@ -3233,7 +3245,7 @@ namespace ts {
}

function convertJsonOptionOfListType(option: CommandLineOptionOfListType, values: readonly any[], basePath: string, errors: Push<Diagnostic>): any[] {
return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => !!v);
return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => option.listPreserveFalsyValues ? true : !!v);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think instead of this we should just check if option is defined and get rid of listPreserveFalsyValues

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would change the behavior of other lists, like types, which could impact existing projects. Why risk exposing them?

To be clear what you're looking for, the filter condition would become v => v !== undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sheetalkamat Would you like to make this change, or is the risk of impacting existing projects too great? Please advise.

PR Backlog automation moved this from Waiting on author to Needs merge Mar 30, 2022
@andrewbranch
Copy link
Member

Thanks @afoxman!

@andrewbranch andrewbranch merged commit 41aca7c into microsoft:main Mar 30, 2022
PR Backlog automation moved this from Needs merge to Done Mar 30, 2022
sidharthv96 added a commit to sidharthv96/TypeScript that referenced this pull request Apr 1, 2022
* upstream/main: (473 commits)
  Correct node used for isDefinition calculation (microsoft#48499)
  fix(48405): emit dummy members from a mapped type (microsoft#48481)
  CFA for dependent parameters typed by generic constraints (microsoft#48411)
  No contextual typing from return types for boolean literals (microsoft#48380)
  fix(47733): omit JSDoc comment template suggestion on node with existing JSDoc (microsoft#47748)
  Ensure that we copy empty NodeArrays during transform (microsoft#48490)
  Add a new compiler option `moduleSuffixes` to expand the node module resolver's search algorithm (microsoft#48189)
  feat(27615): Add missing member fix should work for type literals (microsoft#47212)
  Add label details to completion entry (microsoft#48429)
  Enable method signature completion for object literals (microsoft#48168)
  Fix string literal completions when a partially-typed string fixes inference to a type parameter (microsoft#48410)
  fix(48445): show errors on type-only import/export specifiers in JavaScript files (microsoft#48449)
  Fix newline inserted in empty block at end of formatting range (microsoft#48463)
  Prevent looking up symbol for as const from triggering an error (microsoft#48464)
  Revise accessor resolution logic and error reporting (microsoft#48459)
  fix(48166): skip checking module.exports in a truthiness call expression (microsoft#48337)
  LEGO: Merge pull request 48450
  LEGO: Merge pull request 48436
  fix(48031): show circularity error for self referential get accessor annotations (microsoft#48050)
  Revert "Fix contextual discrimination for omitted members (microsoft#43937)" (microsoft#48426)
  ...
@jircdev
Copy link

jircdev commented Nov 15, 2022

I think this option is very useful, but thinking about it, I have two questions:
There is a way as a consumer to know what bundle I need to consume?

The other stuff is: There may be cases where a module's interface changes depending on the compiled platform, such as IDEs or text editors identifying the types to use

@afoxman afoxman deleted the module-suffix branch November 15, 2022 06:01
@afoxman
Copy link
Contributor Author

afoxman commented Nov 15, 2022

Hi @jircdev, I think your questions fall into the realm of React Native, so I'll answer in that context.

I think this option is very useful, but thinking about it, I have two questions: There is a way as a consumer to know what bundle I need to consume?

It's up to the package producer to establish any rules about which artifacts apply to which platform.

The other stuff is: There may be cases where a module's interface changes depending on the compiled platform, such as IDEs or text editors identifying the types to use

Yes, this is a problem that has yet to be solved. IDEs like VS Code would benefit from having a platform selector which influences how modules (and therefore types) are resolved for browsing. This is a big undertaking, and will require a VS Code plugin and some changes to the TypeScript Server. I don't think it's APIs have enough flexibility to support this scenario.

@jircdev
Copy link

jircdev commented Nov 18, 2022

Hi @afoxman,

I believe I was not clear enough before with the first point, let me clarify it;

Hi @jircdev, I think your questions fall into the realm of React Native, so I'll answer in that context.

I think this option is very useful, but thinking about it, I have two questions: There is a way as a consumer to know what bundle I need to consume?

It's up to the package producer to establish any rules about which artifacts apply to which platform.

I know it not depends on Typescript or React Native but I want to know your opinion.

If I am the creator of a package which I end up building for two platforms (ios and android), How can the dev tools that need to consume this package identify which file must be considered and included? or How can Typescript which is the declaration type to take as valid?

In the NodeJS documentation, they talk about conditional exports and the possibility of adding multiple "target platforms", such as Node, Browser, or Deno but they do include ios or android (that is a requirement beyond of React Native). I think we may use this entry point (conditional exports) to solve the problem in a standardized way and take advantage of it to specify that kind of requirement.

What is your opinion?

@afoxman
Copy link
Contributor Author

afoxman commented Nov 21, 2022

If I am the creator of a package which I end up building for two platforms (ios and android), How can the dev tools that need to consume this package identify which file must be considered and included? or How can Typescript which is the declaration type to take as valid?

In the NodeJS documentation, they talk about conditional exports and the possibility of adding multiple "target platforms", such as Node, Browser, or Deno but they do include ios or android (that is a requirement beyond of React Native). I think we may use this entry point (conditional exports) to solve the problem in a standardized way and take advantage of it to specify that kind of requirement.

What is your opinion?

Node's conditional exports talk about "environment", not "platform", and the examples of default, production, and development suggest this needs to stay separate from the concept of a platform.

I deliberately avoided codifying a standard or even a suggestion about how a React Native package would export both ios and android built artifacts (e.g. transformed TS/Flow files, assets, etc). Getting to a decision there requires broad consensus, and probably a set of working/proven packages that are successfully demonstrating it.

If I had to guess at how it might work, I think the answer starts in react-native.config.js. This is specific to React Native, which makes it appropriately scoped. And there is room in there for platform-specific information. The config schema is defined as part of the React Native CLI. But there is nothing in config today which attempts to define "build artifacts" for a platform+package. So you'd be breaking new ground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
5 participants