TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Type annotations for default export

Open mohsen1 opened this issue 8 years ago • 51 comments
trafficstars

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!

mohsen1 avatar Jan 23 '17 01:01 mohsen1

Would not this be sufficient?

const config: webpack.Configuration  = { 

}
export default config;

mhegazy avatar Jan 23 '17 02:01 mhegazy

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

mohsen1 avatar Jan 23 '17 03:01 mohsen1

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.

mhegazy avatar Jan 23 '17 17:01 mhegazy

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.

DanielRosenwasser avatar Jan 24 '17 01:01 DanielRosenwasser

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!

barakbd avatar Feb 20 '18 01:02 barakbd

import * as webpack from 'webpack'

export default {
...
} as webpack.Configuration

Igor-Gold avatar Mar 10 '19 00:03 Igor-Gold

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

jlouazel avatar Apr 01 '19 16:04 jlouazel

The problem with

export default ... as X

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

pelotom avatar Apr 01 '19 16:04 pelotom

@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

jlouazel avatar Apr 01 '19 16:04 jlouazel

@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.

pelotom avatar Apr 01 '19 16:04 pelotom

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

oli-laban avatar Jun 10 '19 19:06 oli-laban

const as = <T>(value: T) => value

export default as<webpack.Configuration>({
    // ...
})

yarnaimo avatar Jul 19 '19 08:07 yarnaimo

Would love to see this feature for nice succinct code

thethomaseffect avatar Nov 18 '19 11:11 thethomaseffect

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.

mccallofthewild avatar Mar 15 '20 20:03 mccallofthewild

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

Mukhametvaleev avatar Mar 16 '20 15:03 Mukhametvaleev

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
})();

white-room avatar Mar 24 '20 15:03 white-room

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>
})

ackvf avatar Mar 28 '20 14:03 ackvf

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?

LongTengDao avatar Oct 22 '20 06:10 LongTengDao

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.

soullivaneuh avatar Dec 21 '20 11:12 soullivaneuh

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

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

francois-gsk avatar Oct 05 '21 06:10 francois-gsk

what about:

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

?

Great way, works for me, save me some typing

bingtsingw avatar Nov 17 '21 09:11 bingtsingw

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

zaverden avatar Nov 18 '21 04:11 zaverden

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.

richard-ejem avatar Nov 23 '21 14:11 richard-ejem

@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 avatar Nov 24 '21 10:11 francois-gsk

@zaverden that's indeed strange… It looks like an in-between check

francois-gsk avatar Nov 24 '21 10:11 francois-gsk

@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.

richard-ejem avatar Nov 24 '21 10:11 richard-ejem

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

francois-gsk avatar Nov 24 '21 10:11 francois-gsk

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?

a-tarasyuk avatar Nov 26 '21 09:11 a-tarasyuk

@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;

mohsen1 avatar Jan 08 '22 00:01 mohsen1

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

shtse8 avatar Jun 23 '22 14:06 shtse8