microsoft/TypeScript

BUG: Using a globally defined type in a referenced project will cause the types of that file to all be 'any' if imported in another project

ricklove opened this issue · 6 comments

TypeScript Version: 3.1.0-dev.201xxxxx

Search Terms:

global namespace tsc --build type system

Code

// `dependency/types.d.ts`
declare namespace GlobalNamespace {
    type GlobalTypeA = {
        value: string;
    };
}

// `dependency/dep-a.ts`
export type GTypeA = { obj: GlobalNamespace.GlobalTypeA };

// usage/usage-a.ts
// Using a globally defined type in a referenced project will cause the types of that file to all be 'any' if imported in another project.
// However the build executes for the referenced project if built directly.
import { GTypeA } from '../dependency/dep-a';

Expected behavior:

The global type of the dependency should be available in the main project where it is imported as part of a dependent type.

Actual behavior:

All the dependent types in the file which reference a global type definition become any in the main project.

This was initially caused by the following file using the JSX global namespace from react:

// This import is not required to build or to be used in this project without problems.
// However, if this import is not included, it will cause random type system failure in dependent projects.

// import React from 'react';

export type ParentComp<T={}, C={}> = (props: T & { children: C }) => JSX.Element;
export type Comp<T={}> = (props: T) => JSX.Element;

export type CommonComps = {
    View: ParentComp,
    Text: ParentComp<{}, string>,
    EditText: Comp<{ value: string, onChangeText: (text: string) => void, onSubmit?: () => void, onBlur?: () => void }>,
    Button: ParentComp<{ onPress: () => void, alt: string }, string>
};

.d.ts files don't produce any output in outDir. With projectReferences the compiler only looks at outDir if the imported file is part of a referenced project.

I don't know if this behavior is intended, but it kind of makes sense.

To fix your issue, you could rename dependency/types.d.ts to dependency/type.ts. That will produce a .d.ts file in outDir. But it will probably not pick up the declaration file.
For that you could either get rid of the global declaration and use import instead or you may need a /// <reference file="./types" /> in dependency/dep-a.ts.

In the actual case (using react JSX namespace), the global type was actually provided by a node_modules project - which both projects had in their project dependencies. (‘react’ & ‘@types/react’).

So even in this case where the global type was known by both projects, the type system had problems.

Note: I noticed these problems in vscode. The tsc build would sometimes succeed, sometimes not. In vscode, the types would be correct after a reload until I started typing. As soon as I changed any code, the types would go back to ‘any’ for the files that contained any reference to the global namespace.

—-

Another case to consider: my output build folder was at a root location in the folder hierarchy (where it had no node_modules in its modules lookup chain.)

Therefore, the only reference to the node_modules dependency was the presence of the ///reference to ‘react’.

However, I would think this would be sufficient for the type system to include any global types from known ‘react’ modules in the referencing projects scope. But even that would not be correct since the version could be different in the dependency project.

So since the output dir option does not include any additional information about global types from node_modules, I don’t see any way for for the referencing project to understand the ///reference in the generated .d.ts file.

Workaround: Setup project folders as if using project references, but don’t use project references.

Since I already had a source folder structure that mirrored the outDir structure, I decided to remove project references from all the sub projects.

Instead I used a single tsconfig and single package at the root level.

This works perfectly and the tool support was perfect with vscode automatically figuring out and adding imports as needed, etc..

In addition, the build time was instant compared to the ~1-3sec build time when using project references.

The one thing I lose in this structure is the ability to customize the tsconfig per project,
so I have to use tsconfig settings that are as inclusive as possible and my project json needs to list all possible dependencies.

Folder Hierarchy

  • tsconfig.json
  • package.json
  • node_modules
  • project-a
    • src
      • dep-a.ts
  • project-b
    • src
      • usage-a.ts
        • import * from ‘../../project-a/src/dep-a’

@ajafff 's assessment of the situation is correct. If you run into other problems, please open a new issue with repro steps - thanks!

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.