-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Support for all type features in declaration files. #35822
Comments
@RyanCavanaugh I have a large React UI Library written in TypeScript I'd really not like to export all types for props as there is a clear method for grabbing props of a component. Here's my example: interface Props {
children: React.ReactNode;
disabled?: boolean;
fullWidth?: boolean;
intent?: 'primary' | 'secondary' | 'tertiary' | 'neutral' | 'destructive';
onClick?: React.MouseEventHandler<HTMLButtonElement>;
size?: 'smallest' | 'small' | 'medium' | 'large' | 'largest';
}
export function UIButton(props: Props): JSX.Element {
return (
<button
className={clsx('UIButton', {
'UIButton--disabled': props.disabled,
'UIButton--full-width': props.fullWidth,
'UIButton--intent-destructive': props.intent === 'destructive',
'UIButton--intent-neutral': props.intent === 'neutral',
'UIButton--intent-primary': props.intent === 'primary',
'UIButton--intent-secondary': props.intent === 'secondary',
'UIButton--intent-tertiary': props.intent === 'tertiary',
'UIButton--size-large': props.size === 'large',
'UIButton--size-largest': props.size === 'largest',
'UIButton--size-medium': props.size === 'medium',
'UIButton--size-small': props.size === 'small',
'UIButton--size-smallest': props.size === 'smallest',
})}
disabled={props.disabled}
onClick={props.onClick}
>
{props.children}
</button>
);
} and if people need access to the props, they can do so by using The reason why this is an issue for me, is I have a single index file where I |
The changes in #32028 can lead to situations where code works perfectly fine in the editor, then when ready to publish a library, it all breaks when trying to make declaration files (due to the problems in the OP list of issues). (cc @sheetalkamat) |
I also got This normal pattern cause error when export default function withLocaleComponent<
TProps extends WithLocaleComponentInjectedProps
>(Component: React.ComponentType<TProps>) {
class WithLocale extends React.Component<
Omit<TProps, keyof WithLocaleComponentInjectedProps> &
WithLocaleComponentProps
> {
static displayName = getDisplayName("withLocale", Component);
render() {
return (
<LocaleContext.Consumer>
{context => {
let locale = this.props.locale || context.locale;
if (typeof locale === "string") {
const [language, country] = locale.split("-");
locale = { country, language };
}
return (
<Component
{...(this.props as TProps)}
{...context}
locale={locale}
/>
);
}}
</LocaleContext.Consumer>
);
}
}
return hoistStatics(WithLocale, Component);
} Then, I got an idea to create a function mixin that return a class and no error was occurred with function withLocaleMixin<TProps extends WithLocaleComponentInjectedProps>(
Component: React.ComponentType<TProps>
) {
return class WithLocale extends React.Component<
Omit<TProps, keyof WithLocaleComponentInjectedProps> &
WithLocaleComponentProps
> {
static displayName = getDisplayName("withLocale", Component);
render() {
return (
<LocaleContext.Consumer>
{context => {
let locale = this.props.locale || context.locale;
if (typeof locale === "string") {
const [language, country] = locale.split("-");
locale = { country, language };
}
return (
<Component
{...(this.props as TProps)}
{...context}
locale={locale}
/>
);
}}
</LocaleContext.Consumer>
);
}
};
}
export default function withLocaleComponent<
TProps extends WithLocaleComponentInjectedProps
>(Component: React.ComponentType<TProps>) {
return hoistStatics(withLocaleMixin<TProps>(Component), Component);
} My question is, is it ok to use the second pattern (with |
ERROR in (..)/node_modules/@prezly/slate-editor/build/components/FloatingMenu/index.d.ts(1,25): TS2307: Cannot find module './FloatingMenu' or its corresponding type declarations. ERROR in (..)/node_modules/@prezly/slate-editor/build/components/LoadingPlaceholderV2/index.d.ts(1,25): TS2307: Cannot find module './LoadingPlaceholderV2' or its corresponding type declarations. See: microsoft/TypeScript#35822
ERROR in (...)/node_modules/@prezly/slate-editor/build/components/TooltipV2/index.d.ts(1,32): TS2307: Cannot find module './TooltipV2' or its corresponding type declarations. See: microsoft/TypeScript#35822
I updated the original post with a |
…hat we can output declaration files lowclass/Mixin and TypeScript "private" or "protected" access modifiers cause declaration output not to be possible, thus forcing us to point package.json types at src/index.ts instead of dist/index.d.ts, which can cause type errors in downstream consumers if they use different tsconfig.json settings when they type-check their applications (coupled with the fact that TypeScript will type-check library code if the library code types field does not point to declaration files, *even if* the user set `skipLibCheck` to `true`). Outputting declaration files and pointing `types` to them is the way to go for publishing libraries, but certain features of TypeScript do not work when declaration output is enabled, which is why we had to refactor. microsoft/TypeScript#35822 Now that lume/cli is updated, npm test fails. Needs a fix.
… fields such that we can output declaration files lowclass/Mixin and TypeScript "private" or "protected" access modifiers cause declaration output not to be possible, thus forcing us to point package.json types at src/index.ts instead of dist/index.d.ts, which can cause type errors in downstream consumers if they use different tsconfig.json settings when they type-check their applications (coupled with the fact that TypeScript will type-check library code if the library code types field does not point to declaration files, *even if* the user set `skipLibCheck` to `true`). Outputting declaration files and pointing `types` to them is the way to go for publishing libraries, but certain features of TypeScript do not work when declaration output is enabled, which is why we had to refactor. microsoft/TypeScript#35822 Now that lume/cli is updated, npm test fails. Needs a fix.
Keeping the discussion going, copied from #44360: I found it very discouraging, that TypeScript is currently fragmented into 2 languages. The 1st one is You can express more logic in Despite being a well-known issue, this fragmentation is not documented anywhere, and TS development team does not provide much feedback about it. It is a hushed up thing, nobody wants to talk about this. This issue should, at the very least, be described in details, on the documentation website. The solution is simple - a design decision should be made, that Its very discouraging, when you have to spent hours, mingling your code (that compiles just fine in |
Documenting the difference sounds good, maybe somewhere here? https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html |
Yes, looks like an appropriate place. Just need to make it clear, that only a subset of regular |
As suggested by @trusktr in the thread, changing the way of declaration files generation to just stripping the implementation and keeping only types if probably the best solution to the problem. Meanwhile, even if heading to the route of fixing the existing solution, it seems like most pieces of the puzzle are already there. As mentioned in the thread, anonymous types can be solved with #30979 The other part is related to the fact, that the type of internal mixin class is represented with the object. Of course that would lead to error. For example, for this mixin: export type AnyFunction<A = any> = (...input: any[]) => A
export type AnyConstructor<A = object> = new (...input: any[]) => A
export type Mixin<T extends AnyFunction> = InstanceType<ReturnType<T>>
export const MyMixin = <T extends AnyConstructor>(base : T) =>
class MyMixin extends base {
method () : this {
return this
}
}
export type MyMixin = Mixin<typeof MyMixin> The generated declaration is: export declare type AnyFunction<A = any> = (...input: any[]) => A;
export declare type AnyConstructor<A = object> = new (...input: any[]) => A;
export declare type Mixin<T extends AnyFunction> = InstanceType<ReturnType<T>>;
export declare const MyMixin: <T extends AnyConstructor<object>>(base: T) => {
new (...input: any[]): {
method(): this;
};
} & T;
export declare type MyMixin = Mixin<typeof MyMixin>; Which leads to error But, there's another "green" PR: #41587 which can be used to fix this. This PR should also fix the well-known problem of private/protected methods. With this PR, the declarations for the
So I believe, fixing this ticket is a matter of proper prioritization and there's no irresistible technical problems in it. This issue clearly causes a lot of struggle for a lot of people, and there's clearly no intention, to even discuss the ways of fixing it. Dear TS team, why this attitude? |
@RyanCavanaugh I have to say, I'm also disheartened by this. A team could spend a long time building a library and assuming that "if it compiles, it'll work!" only to find out after turning |
Declaration files can only express a subset of TS. microsoft/TypeScript#35822
I'm on TS 3.7.3 at time of writing this.
It seems like this issue should simply be focused on and fixed before continuing to add more and more features to the language and widening the source vs declaration gap.
This is quite a problem!
Search terms
"typescript declaration file limitations"
"has or is using private name"
"exported class expression may not be private or protected"
"Property 'foo' of exported class expression may not be private or protected. ts(4094)"
"Return type of exported function has or is using private name 'foo'. ts(4060)"
Related issues (a small fraction of the results on Google) in no particular order:
declaration: true
#17293 (@saschanaz)Workaround
One way to work around all of the above issues, for libraries, is to have library authors point their
types
field inpackage.json
to their source entrypoint. This will eliminate the problems in the listed issues, but has some big downsides:dist/index.d.ts
) but withtypes
pointing to a source file (f.e.src/index.ts
) then if the consumer'stsconfig.json
settings are not the same as the library's, this may cause type errors (f.e. the library hadstrict
set tofalse
while being developed, but the consumer setsstrict
totrue
and TypeScript picks up all the strict type errors in the library code)..ts
file in node_modules results in the file being compiled and type checked #35744skipLibCheck
is set to true, the consumer project's compilation will still type-check the (non-declaration) library code regardless.skipLibCheck: true
is ignored when package'stypes
field points to a.ts
file (not a.d.ts
declaration file) #41883With those two problems detailed, if you know you will be working in a system where you can guarantee that all libraries and consumers of those libraries will be compiled with the same compiler (
tsconfig.json
) settings (i.e. similar to Deno that defines compiler options for everyone), then pointing thepackage.json
types
field to a source file (because declaration output is not possible) is currently the workaround that allows all the unsupported features of declaration files to be usable. But this will fail badly if a library author does not know what compiler settings library consumers will have: everything may work fine during development and testing within external examples, but a downstream user will eventually report type errors if they compile their program with different settings while depending on the declaration-less libraries!Suggestion
Support for all type features in declaration files.
Please. 🙏 I love you TS team, you have enabled so much better code dev with TypeScript. ❤️ 😊 TS just needs declaration love for support of all features of the language.
Some work is being done, f.e. #23127, but overall I feel that too much effort is being put onto new language features while declaration output is left behind.
This creates a poor developer experience when people's working code doesn't work (the moment they wish to publish it as a library and turn on
declaration: true
).I hope the amazing and wonderful TS team realize how frustrating it may feel for someone to work on a project for months, only to end with un-fixable type errors the moment they want to publish the code as a library with
declaration: true
, then having to abandon features and re-write their code, or try to point package.json"types"
to.ts
files only to face other issues.I wish every new feature of the language came paired with working tests for equivalent declaration emit. Can this be made a requirement for every new language feature, just like unit tests are a requirement?
Use Case
Use any language feature, publish your code, then happily move along, without facing issues like
I worked hard for months to get the typings in the above code working, then I turned on
"declaration": true
and TypeScript said "today's not the day!".I hope you the team can see that these issues are a pain, and realize the need to bring declaration emit to parity with language features and not letting the gap between language features and declaration output widen.
Examples
The issue that sparked me to write this was #35744.
In that issue there's an example of what implicit return types might look like:
We'd need solutions for other problems like the
protected
/private
error above, etc.Of course it'll take some imagination, but more importantly it will take some discipline: disallow new features without declaration parity.
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: