Skip to content
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

Empty namespace are removed causing code to break at runtime #1158

Closed
eddyw opened this issue Apr 16, 2021 · 2 comments · Fixed by #1161
Closed

Empty namespace are removed causing code to break at runtime #1158

eddyw opened this issue Apr 16, 2021 · 2 comments · Fixed by #1161

Comments

@eddyw
Copy link

eddyw commented Apr 16, 2021

Description

I'm using TS namespaces in a project for the sole purpose of organizing code. The namespaces are technically empty at runtime (after build) and only contain type declarations. I've noticed my code failing because empty namespace's are removed even if you have a reference to it.

Here is a minimal reproduction:

Example input:

namespace Something {
	export declare function Print(a: string): void
}

Something.Print = function(a) {}

export const { Print } = Something

TypeScript compiler output:

var Something;
(function (Something) {
})(Something || (Something = {}));
Something.Print = function (a) { };
export const { Print } = Something;

esbuild transform output:

Something.Print = function(a) {
};
export const {Print} = Something;

This results in a ReferenceError thrown at runtime: ReferenceError: Something is not defined because the namespace is dropped because it's empty even though there is a reference to it (Something.Print = ... and export const { ... } = Something)

Expected output

Probably the same as TSC?

var Something;
(function(Something2) {
})(Something || (Something = {}));
Something.Print = function (a) {
};
export const {Print} = Something;

Version

ESBUILD VERSION: 0.11.11

@evanw
Copy link
Owner

evanw commented Apr 16, 2021

Thanks for the report. So namespaces in TypeScript are really weird. The "even if you have a reference to it" part isn't a reason to retain the namespace because namespaces that are empty and namespaces that only contain type declarations are not retained even if you have a reference to them. Also export declare in a namespace behaves differently for local variables than it does for other declarations:

namespace ns {
  export declare let L: any
  console.log(L)

  export declare function F(): any
  console.log(F)

  export declare class C {}
  console.log(C)

  export declare enum E {}
  console.log(E)

  export declare namespace N {}
  console.log(N)
}

The TypeScript compiler compiles that code to this:

var ns;
(function (ns) {
    console.log(ns.L);
    console.log(F);
    console.log(C);
    console.log(E);
    console.log(N);
})(ns || (ns = {}));

As you can see, variables are special and behave differently than other declarations. So this is why esbuild's parser supports exports declare for variables but not for other declarations.

However, I wasn't aware of the case reported in this issue. I can potentially try to make this work. Alternatives in the meantime include using export declare let instead of export declare function or just using export function Print() instead.

@eddyw
Copy link
Author

eddyw commented Apr 18, 2021

@evanw hm, I actually realized just now that declare cannot always be removed safely (corner cases). It basically just hints the compiler "I know there is X var/let/const/function in Y scope"

When using declare let, we hint the compiler that we know there is a block scoped variable, in this case, within the namespace. That's why the behavior is quite different from declare function.

Edit: ref https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html

Btw, nice work. Esbuild is basically the only reason why I'm learning Go 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants