Skip to content

Declarations and "Exported variable 'XXX' has or is using name 'YYY' from external module "ZZZ" but cannot be named." #24666

Description

I was pleased to see #9944 had been closed with what looked like a feature of 2.9 'fixing' the issue. However, it still doesn't seem to be working as I'd expect. Take this example:

import styled from "styled-components";

export const Container = styled.div`
    background: red;
`;

With a normal TypeScript project, using the tsconfig default except enabling declarations. I get three errors:

src/styles.ts(3,14): error TS4023: Exported variable 'Container' has or is using name 'React.ClassAttributes' from external module "/node_modules/@types/react/index" but cannot be named.
src/styles.ts(3,14): error TS4023: Exported variable 'Container' has or is using name 'React.HTMLAttributes' from external module "/node_modules/@types/react/index" but cannot be named.
src/styles.ts(3,14): error TS4023: Exported variable 'Container' has or is using name 'StyledComponentClass' from external module "/node_modules/styled-components/typings/styled-components" but cannot be named.

The obvious solution seems to be importing the types involved:

import styled, { StyledComponentClass } from "styled-components";
import * as React from "react";

export const Container = styled.div`
    background: red;
`;

Which would be fine, I suppose (though I don't see why it is necessary..?) but this doesn't work with noUnusedLocals as you get more errors:

src/styles.ts(1,18): error TS6133: 'StyledComponentClass' is declared but its value is never read.
src/styles.ts(2,13): error TS6133: 'React' is declared but its value is never read.

... However you can work around that by explicitly using the types, but even with this basic example it gets absurdly verbose:

import styled, { StyledComponentClass } from "styled-components";
import * as React from 'react';

export const Container: StyledComponentClass<React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement>, any, React.ClassAttributes<HTMLDivElement> & React.HTMLAttributes<HTMLDivElement>> = styled.div`
    background: red;
`;

What is the recommended solution / workaround to this?

Activity

  1. AnyhowStep commented on Jun 5, 2018

    @AnyhowStep
    Contributor

    I can't find the comment I made on a workaround anymore. It was on a similar issue but even worse, because when this happens with generic types, you generally find yourself losing out on type inference.

    The workaround was to use the imported type with a dummy variable if it's an interface or type alias.

    If it's a class, you can get away with NameOfClass; as a statement and it counts as a usage.

    With interfaces and the like,

    import {SomeGenericInterface} from "thing";
    
    const dummy : SomeGenericInterface<any, any, any>|undefined = undefined;
    dummy;

    I'm on my phone so I'm not sure if I've described it properly.


    This way, type inference still works and you've imported and used the type. but it's extremely ugly and hacky. And, in general, you don't know what needs to be imported and used this way until you want to compile the code...

    I wish there was a way for it to automatically import during the compile phase because this workaround I use has given me no problems.

    It's just repeatedly,

    1. Run tsc
    2. See that error?
    3. Import type
    4. Create dummy variable of imported type union undefined
    5. GOTO 1

    So, I've got it down to an algorithm but you might end up needing to do this for hundreds of files, to compile. And you don't need to do this at all with --noEmit or to run with ts-node

  2. jamiewinder commented on Jun 5, 2018

    @jamiewinder
    Author

    Thanks, AnyhowStep - that's very similar to what I do at the moment too:

    ((_1?: StyledComponentClass<any, any>, _2?: typeof React) => 0)();

    🤢

    I've got dozens of files that start this way.

  3. AnyhowStep commented on Jun 5, 2018

    @AnyhowStep
    Contributor

    I like your way better, actually.

  4. AnyhowStep commented on Jun 5, 2018

    @AnyhowStep
    Contributor

    I just tried something and it seemed to work (I haven't exhaustively tested this so I'm not 100% sure)

    Just do,

    import * as package1 from "some-package-1";
    import * as package2 from "some-package-2";
    package1;
    package2;

    Assuming package1 andpackage2 contain interfaces/type aliases/classes, that you need for emitting. This helps if there are many interfaces/type aliases/classes in a package that are needed.

    Doesn't help so much if you only need the one type.

  5. weswigham commented on Jun 14, 2018

    @weswigham
    Member

    Jamie Winder (@jamiewinder) Thanks for the report! Apologies - looks like our visibility checking logic in declaration emit didn't handle things aliased via export= correctly (as is the case for the React declaration file), so is exhibiting the old behavior for now. I should have a fix up tomorrow.

  6. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptDomain: Declaration EmitThe issue relates to the emission of d.ts filesFixedA PR has been merged for this issue

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions