microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.02k stars 12.49k forks source link

Type annotations for default export #13626

Open mohsen1 opened 7 years ago

mohsen1 commented 7 years ago

TypeScript Version: 2.1.1

Code

import * as webpack from 'webpack';
export default: webpack.Configuration {
};

Expected behavior: No error

Actual behavior:

[ts] Expression expected. error at default:

I couldn't find an issue for this but it's very likely it's a duplicate


Please ๐Ÿ‘ on this issue if you want to see this feature in TypeScript and avoid adding "me too" comments. Thank you!

mhegazy commented 7 years ago

Would not this be sufficient?

const config: webpack.Configuration  = { 

}
export default config;
mohsen1 commented 7 years ago

Yes, that's what I do know but I wish I didn't have to.

mhegazy commented 7 years ago

related to https://github.com/Microsoft/TypeScript/issues/3792. we have tried to keep the module export as simple as possible on the syntax side.

DanielRosenwasser commented 7 years ago

I think it's a little more related to to the following scenario regarding function declarations.

if I want to write a decorator that is verified against the PropertyDecorator type in lib.d.ts, I can't write it easily. I have to use a function expression.

export let Encrypt: PropertyDecorator = function (obj, propName) {
};

which is case of friction whenever I want to actually implement a decorator without making a mistake.

barakbd commented 6 years ago

I am trying to use a default export in the following way


readdirSync(join(__dirname, "../controllers/box"))

  .filter(f => f !== "*.spec.ts")
  .forEach(controllerFile => {
    const controllerBaseName = basename(controllerFile, ".js")
    import(`../controllers/box/${controllerBaseName}`).then((controller)=>{
      appRouter.use(
        `/box/${controllerBaseName}`, controller.default(boxServiceAccountClient))
    }).catch(error=>{
      console.log(error)
    })
  }); //end forEach

I get an error:

TypeError: controller.default is not a function at fs_1.readdirSync.filter.forEach.Promise.resolve.then.then.controller (/Users/bbendavi/Documents/cdt-box/dist/server/config/routes.js:16:71) at <anonymous> at process._tickCallback (internal/process/next_tick.js:160:7) at Function.Module.runMain (module.js:703:11) at startup (bootstrap_node.js:193:16) at bootstrap_node.js:617:3

I asked this on SO with a full file examples: https://stackoverflow.com/questions/48696327/how-to-define-a-module-default-export-type-in-typescript

Would much appreciate your help!

IgorGee commented 5 years ago
import * as webpack from 'webpack'

export default {
...
} as webpack.Configuration
jlouazel commented 5 years ago

@IgorGee That's the proper way of doing it IMHO

pelotom commented 5 years ago

The problem with

export default ... as X

is that it's a cast, so it purposely loses type safety.

jlouazel commented 5 years ago

@pelotom As a general matter of fact, you're completely right, I'd avoid putting this in my codebase. For the scope of this thread where this is about webpack configuration, I think we're just fine

pelotom commented 5 years ago

@jlouazel leaving aside whether type safety is any less important in a webpack config... the issue is not specific to webpack, it's about annotating the type of the default export in general.

oli-laban commented 5 years ago

@IgorGee That's forbidden in @typescript-eslint/recommended.

yarnaimo commented 5 years ago
const as = <T>(value: T) => value

export default as<webpack.Configuration>({
    // ...
})
thethomaseffect commented 4 years ago

Would love to see this feature for nice succinct code

mccallofthewild commented 4 years ago

If you're exporting a function, put it in parenthesis before the as. e.g.

export default ((req, res) => {
   // Intellisense Enabled on `req` & `res`!
   return 'Hello World!';
}) as RequestHandler;

๐ŸšฉEdit for downvoters: Typescript does check functions for return type & parameter compatibility when typecasting. Unlike typecasting for object types, functions retain a degree of type safety.

Mukhametvaleev commented 4 years ago

I have the same problem too. Need to type default export instead of cast.

white-room commented 4 years ago

could also use an iife so that the type is at the beginning of the export rather than the end

export default ((): MyType => ({
  k: v
})();
ackvf commented 4 years ago

While this gives me type hints inside the function (thanks @mccallofthewild )

export default (({ withIcon, children }) => {
  return <SomeJSX withIcon={withIcon}>{children}</SomeJSX>
}) as React.FC<{withIcon: boolean}>

I would still prefer to have the type declared up front (not sure about the syntax here though)

export default: React.FC<{withIcon: boolean}> (({ withIcon, children }) => {
  return <SomeJSX withIcon={withIcon}>{children}</SomeJSX>
})
LongTengDao commented 4 years ago

Maybe export const default :Type = value; export type default = Type; export interface default {} could bring us more uniformity, avoid to introduce a new set of grammars just for default?

soullivaneuh commented 3 years ago

The solution proposed by @IgorGee works but is not fully reliable for type checking.

My default export is a ResourceProps where the property name is required.

If I try with a typed constant (the actual only solution):

const resourceProps: ResourceProps = {};
export default resourceProps;

I will have:

Property 'name' is missing in type '{}' but required in type 'ResourceProps'.  TS2741

But nothing if I do it like this:

export default {} as ResourceProps;

This make things harder to debug if something goes wrong because of this missing property. And we are using Typescript for that, right? :-)

However, I also understand the need to type the default export. On some cases, we just return a configuration object like that:

const resourceProps: ResourceProps = {
  name:"users",
  icon: UserIcon,
  list: UserList,
  create: UserCreate,
  edit: UserEdit,
  options:{
    label: 'Utilisateurs',
  },
};

export default resourceProps;

It's a bit cumbersome to be forced to declare a new variable just to take benefit of typing.

That was my two cents. I wanted to illustrate with some samples because I was myself a bit confused as a TypeScript beginner when I saw the lot of down votes on the previous solutions.

francois-gsk commented 3 years ago

what about: export default <MyType>{ prop: 'value' };ย ?

Edited: no a good solution, it's a reverse assertion, it's even worst than as MyType.

bingtsingw commented 2 years ago

what about:

export default <MyType>{
  prop: 'value',
};

?

Great way, works for me, save me some typing

zaverden commented 2 years ago

what about:

export default <MyType>{
  prop: 'value',
};

?

interface MyType {
  prop: string;
  prop2: string;
}

const toExport: MyType = {
  prop: "value",
}

export default <MyType>{
  prop: "value",
};

in this example TS fails on toExport variable (Property 'prop2' is missing...) and passes on export default <MyType>{...}, but should fail too

richard-ejem commented 2 years ago

what about:

export default <MyType>{
  prop: 'value',
};

?

It is not a type declaration but a type assertion. In other words, literally the same as as MyType, just another syntax. It was already discussed and discouraged here.

francois-gsk commented 2 years ago

@richard-ejem I fully disagree, it's not a type assertion:

interface MyType {
  hello: string;
}

export default <MyType>{ hello: false }; // ๐Ÿ‘‰ Type Error

export default <MyType>{ hello: 'world!' }; // ๐Ÿ‘‰ Type Checked

Unlessโ€ฆ you're rightโ€ฆ it's a reversed assertion. It's even worst because in the case of missing props, there is no error whatsoever, but with as MyType there is still a type error.

francois-gsk commented 2 years ago

@zaverden that's indeed strangeโ€ฆ It looks like an in-between check

richard-ejem commented 2 years ago

@richard-ejem I fully disagree, it's not a type assertion:

interface MyType {
  hello: string;
}

export default <MyType>{ hello: false }; // ๐Ÿ‘‰ Type Error

export default <MyType>{ hello: 'world!' }; // ๐Ÿ‘‰ Type Checked

Yes, it is an assertion, replace it with as and you get the same result.

interface MyType {
  hello: string;
}

export default { hello: false } as MyType; // ๐Ÿ‘‰ Type Error

export default { hello: 'world!' } as MyType; // ๐Ÿ‘‰ Type Checked

The type error is there because assertions between completely incompatible types are illegal, please read the TS error message you get.

see https://www.tutorialsteacher.com/typescript/type-assertion , <TYPE> is old syntax for assertions, now rarely used because its syntax conflicts with JSX.

francois-gsk commented 2 years ago

@richard-ejem Yes, I edited my initial comment to reflect that it's not a valid notation ๐Ÿ˜‰ Thanks.

a-tarasyuk commented 2 years ago

I think using semantics export default: Type Expression might add some restrictions, for instance, array definition with spaces between type and []. Using spaces between type and [] is allowed to define array type

const a: number         [];

How this case should be handled in export default? Should spaces between type and [] be forbidden? For the following example I expect an error that array is not assignable to number;

export default: number [];

It seems that the main concern with using the as operator is that it simply casts to a type without checking for type compatibility.

There is a draft proposal for adding the new satisfies operator that might fix this issue - https://github.com/microsoft/TypeScript/pull/46827.

It allows checking type like so

interface Foo {
  a: number;
}
export default {} satisfies Foo; 
                            ^^^
Type '{}' does not satisfy the expected type 'Foo'.
  Property 'a' is missing in type '{}' but required in type 'Foo'.(1360)

Example

@RyanCavanaugh @DanielRosenwasser What do you think? Can we add this case to https://github.com/microsoft/TypeScript/pull/46827 proposal use cases?

mohsen1 commented 2 years ago

@a-tarasyuk I don't think syntax is ambiguous.

export default: number[];
// same as 
type Default = number[];
export default Default;
export default: number[] = [];
// same as 
const Default: number[] = [];
export default Default;
shtse8 commented 2 years ago

I am confusing about this too. any suggestion about typing the default export function?

dobesv commented 2 years ago

I am confusing about this too. any suggestion about typing the default export function?

Currently you have to put it into a const first, then export it:

const dftExport: SomeType = { ... };
export default dftExport;

This issue is tracking a request to implement some way to avoid the extra repetition.

dobesv commented 2 years ago

The use case I'm looking at for this is to type and name a function that's a default export, without repeating the function name:

export interface MyComponentProps {
   yes: boolean;
}
export default function MyComponent(props) {

} satisfies SFC<MyComponentProps>;
jchook commented 2 years ago

@ackvf's syntax suggestion seems like the best proposed so far.

export default: React.FC<{withIcon: boolean}> (({ withIcon, children }) => {
  return <SomeJSX withIcon={withIcon}>{children}</SomeJSX>
})

Would that be difficult to implement?

export default: {type} {value}
dobesv commented 2 years ago

I guess another option could be to allow "default" as a variable name to export.

export const default: React.FC<MyProps> = function MyComponent(props) { ... }
NikolaRHristov commented 2 years ago

This is type checked:

import type { Config } from "package";

export default (): Config => {
    return {
        properties: true
    };
};
soullivaneuh commented 2 years ago

@NikolaRHristov Yes, but this works only if you export a function, you are typing its return type.

sidvishnoi commented 1 year ago

Re: https://github.com/microsoft/TypeScript/issues/13626#issuecomment-478648204

I think this works well enough with satisfies?

type MyType = { name: string }

export default { name: 5 } satisfies MyType;
// Type 'number' is not assignable to type 'string'.(2322)

export default { name: "world", foo: 4 } satisfies MyType;
// Object literal may only specify known properties, and 'foo' does not exist in type 'MyType'.(1360)

export default { name: "world" } satisfies MyType;
// OK
dobesv commented 1 year ago

Will satisfies allow typescript to infer argument and return types in something like the following?

export interface MyComponentProps { foo: string }
export default function MyComponent(props) {  return <> ... </>; }  satisfies React.SFC<MyComponentProps>;
sidvishnoi commented 1 year ago

@dobesv https://github.com/microsoft/TypeScript/issues/13626#issuecomment-1409060244

type RequestHandler = (req: string, res: number) => string;

export default ((req, res) => {
   return 1;
}) as RequestHandler;
// Conversion of type '(req: string, res: number) => number' to type 'RequestHandler' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
//   Type 'number' is not comparable to type 'string'.(2352)
// and the whole block is highlighted as error.

export default ((req, res) => {
   return 1;
}) satisfies RequestHandler;
// Type '(req: string, res: number) => number' does not satisfy the expected type 'RequestHandler'.
//  Type 'number' is not assignable to type 'string'.(1360)
// but "RequestHandler" is highlighted as error (I would expect the return value to be highlighted).

Playground Link

dobesv commented 1 year ago

I think that satisfies this request to my satisfaction, in that case.

mon-jai commented 1 year ago

@Sid Vishnoi It works on function declarations too.

export default (function (req, res)  {
   return 1;
}) satisfies RequestHandler;

Playground Link

dragomirtitian commented 1 year ago

Just want to point out that using both as and satisfies might actually be the best solution here until we get actual syntax. satisfies checks that the value conforms to the type, as forces the type instead of allowing TS infer its own type

type Union = { v: 1 | 2 }
export default { v: 1 } satisfies Union as Union; // Checked against Union and Union is preserved as the type of the export.

Playground Link

Thanks to @acutmore for suggesting this use of satisfies T as T

rampion commented 1 year ago

Another workaround is to pass the export value to an identity function to check its value:

function verify<T>(t: T): T { return t }

export default verify<React.FC<IProps>>(function(props) {
  // ...
})

I'm not sure if there's a canonical identity function that'd avoid introducing your own.

ekcom commented 1 year ago

For now, we continue to export default exportDefault; with something like const exportDefault: webpack.Configuration = {};.

dobesv commented 1 year ago

satisfies is a type assertion. It does not type the variable

Oh, too bad. I thought an earlier commenter said that it did type it. We haven't upgraded to the version of typescript with this yet, but this was one thing I was looking forward to.

mccallofthewild commented 1 year ago

You can also declare a typed variable and assign it in your export.

type Union = { v: 1 | 2 };

let d: Union;

export default d = {
  v: 1,
};
mon-jai commented 1 year ago

@mccallofthewild Then it could be reassigned.

mccallofthewild commented 1 year ago

@mon-jai No. ๐Ÿ˜ That's not how assignment works.

let a;
let b = a = 1;
a = 2;
console.log(b == a);
// `false`
mon-jai commented 1 year ago

Shouldn't the following code work?

// d.js
let d: number;
export default d = 1:

// Other files
import d from "d.js";
d = 2;
MacroMan commented 10 months ago

satisfies T as T doesn't reduce boilerplate in all circumstances. eg using a type from a value doesn't work at all:

export default {some:'value'} satisfies SomeType<typeof SomeValue> as SomeType<typeof SomeValue>

and even if it did work, I'm repeating my type declaration twice.

Doing export default: SomeType<typeof SomeValue> {some:'value'} would be great for reducing boilerplate