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

Re-export module namespace as global #14051

Open
evmar opened this issue Feb 13, 2017 · 19 comments
Open

Re-export module namespace as global #14051

evmar opened this issue Feb 13, 2017 · 19 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@evmar
Copy link
Contributor

evmar commented Feb 13, 2017

We're attempting to migrate users within Google to new @types typings, which move most typings into modules, and having a ton of trouble. The basic problem is that a module (e.g. CodeMirror) used to declare a global complex interface:

declare function CodeMirror(...);
declare namespace CodeMirror { ... }

and now they are modules:

export = CodeMirror;

and other typings refer to them as modules (so we can't just undo that export statement) but we also have tons of older user code that doesn't refer to them as modules. Ideally we'd be able to make it work both ways while we migrate users incrementally.

I appreciate that you don't want the language to make this convenient, but I'm having difficulty finding any way to make it work temporarily while we migrate.

Some things I tried:

  1. Aliasing the module into the global namespace
declare global {
  import CodeMirror = require('codemirror');
}

(and other variants) fails with "Imports are not permitted in module augmentations.".

  1. Making the module global first, then aliasing it into a module namespace.
    If you wrap the file contents with a "declare global {", then I couldn't figure out a way to repackage that global as a module again. The export = CodeMirror; fails with "Cannot find name 'CodeMirror'." despite the global CodeMirror being declared in the same file.

Do you have any advice? Note that "export as namespace" doesn't help because our user code is already modules, not scripts.

@mhegazy
Copy link
Contributor

mhegazy commented Feb 13, 2017

I am not sure i understand the issue, so this might be useless to you, but if CodeMirror is supposed to be accessed as a global, why not add export as namespace CodeMirror in the declaration file? if it is not, how was that code working in the first place?

@evmar
Copy link
Contributor Author

evmar commented Feb 13, 2017

The declaration file does have "export as namespace CodeMirror", but that does not help. The to-be-upgraded user code is already written as modules (so it doesn't see the global codemirror) but does not have an import of CodeMirror (so it doesn't get the module one either).

@evmar
Copy link
Contributor Author

evmar commented Feb 13, 2017

Smaller repro of the problem.

  1. Say the new version of foo.d.ts that we're trying to upgrade to has this:
export = Foo;
export as namespace Foo;

declare function Foo(x: number);
declare namespace Foo {
  namespace Bar {
    var baz: number;
    interface IF { a: number }
  }
}
  1. Suppose the upgrade includes other code that writes import * as foo from 'foo'.

  2. Finally, suppose there are many users like

export {}  // is a module already
Foo.Bar.baz;  // expects a global Foo
let x: Foo.Bar.IF;  // that includes namespace elements

Currently these don't compile together as written, and we want to eventually migrate all the various different code that looks like #3 to use an import statement. But in the interim we can temporarily modify #1, and I can't find an invocation that works in a way that allows us to incrementally migrate #3.

@blakeembrey
Copy link
Contributor

blakeembrey commented Feb 13, 2017

How about something like this? Have a file that declares all the globals (assuming I'm reading the requirement right).

import * as _foo from 'foo'

declare global {
  var foo: typeof _foo;
}

@evmar
Copy link
Contributor Author

evmar commented Feb 13, 2017

Thanks for looking! That works to re-export the foo value, but loses the namespace members.
E.g. if inside Bar there is an interface XXX, then with your amendment the user code still cannot do let x: Foo.Bar.XXX;. I'm gonna modify the above snippet to make this more obvious.

@blakeembrey
Copy link
Contributor

blakeembrey commented Feb 13, 2017

@evmar If you can't do that, it means the definition was written incorrectly and it's not really accessible anyway. Those definitions would need to be fixed up. Do you have an example of what doesn't work and I can point out where to fix it up for use as an external module?

@evmar
Copy link
Contributor Author

evmar commented Feb 13, 2017

Here's an example of the nested namespaces in the d.ts:
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8e2708ca2118472ca8a3071c21bfe7a5250b911d/codemirror/index.d.ts#L1154
How are they wrong? Can you suggest how I should fix them up?

@blakeembrey
Copy link
Contributor

blakeembrey commented Feb 13, 2017

That looks fine, you should be able to access that by CoreMirror.MergeView .MergeViewEditorConfiguration (where CodeMirror is whatever you declared globally in declare global).

Wasn't meant to imply they were definitely wrong, but if you need to access types and you can't it means that type was accidentally hidden in the definition (e.g. by not exporting it). I've seen that done a lot and was all I meant by "wrong". E.g.

// Wrong (`Bar` is never exported).
declare namespace X {}
declare interface Bar {}
export = X

// Better (`Bar` is part of the exported namespace, can be accessed externally).
declare namespace X {
  export interface Bar {}
}
export = X

@evmar
Copy link
Contributor Author

evmar commented Feb 13, 2017

@blakeembrey if you try the example snippet I posted above, you'll see it doesn't work with your declare global suggestion. The problem is that the var foo: typeof _foo; only exports the value but not the namespace.

I believe the TS syntax for aliasing the value+type+namespace is the import = syntax I wrote as option 1 in the original report, which also fails for other reasons.

@blakeembrey
Copy link
Contributor

blakeembrey commented Feb 14, 2017

@evmar It should work, I've used it a dozen times - a lot of modules in @types are written in this style. If you can link me to what explicitly you're writing, I can test it for you. For example, it works fine for me:

image

Edit: To prove that it is using the namespace I provided, here it is using Foo:

image

@evmar
Copy link
Contributor Author

evmar commented Feb 14, 2017

Edit: I wrote a long comment here but it was wrong because I have failed at cut and paste, let me get back to you.

@evmar
Copy link
Contributor Author

evmar commented Feb 14, 2017

I had a combination of failing other piece, but the critical part was that to augment this module, it appears you must

import cm = require('./foo');
declare module "./foo" {
  ... stuff here ...
}

and not do

declare global {
  namespace Foo {
    ... stuff here ...
  }
}

So, to summarize, to make a typings module into a global:

  1. leave original module unchanged

  2. add file

import m = require('mymod');
declare global {
  var mymod: typeof m;
}
  1. if you want to augment, you must augment the import module, not the global one, so change clients that looked like
namespace mymod {
  ...
}

to instead say

import m = require('mymod');
declare module 'mymod' {
  ...
}

Thanks for your help, Blake!

@evmar evmar closed this as completed Feb 14, 2017
@fightingcat
Copy link

fightingcat commented Jun 24, 2017

import * as _foo from 'foo'
declare global {
  var foo: typeof _foo;
}

It doesn't work with let something: foo.SomeType, claim Cannot find namespace 'foo'.

@olee
Copy link

olee commented Feb 21, 2018

Same problem - it doesn't reexport the namespace types.

@mhegazy mhegazy added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Feb 21, 2018
@mhegazy mhegazy reopened this Feb 21, 2018
@mhegazy
Copy link
Contributor

mhegazy commented Feb 21, 2018

I would like to re-discuss allowing import aliases in `declare global blocks:

declare global {
    import foo = N;
}

@fayezmm
Copy link

fayezmm commented May 27, 2018

This works for me:

import * as foo from "./foo";
export { foo }; 

@fightingcat
Copy link

It's not very handy but working:

// imports.d.ts
import * as fs from 'fs';
export import fs = fs;
export as namespace imports;
// global.d.ts
import fs = imports.fs;
// another .ts file
fs.mkdtempSync('temp');
let stream: fs.ReadStream;

@MicahRamirez
Copy link

@fightingcat Can you explain the syntax at all ?

I thought as soon as you import a module it is scoped to the module, but I am able to reference this module as if it was ambient. Why ?

I am using your workaround with dataloader to re-export members that I want availability globally. I may be passing around options for a dataloader in a module that doesn't actually create the dataloader so not reference the dataloader module explicitly.

// index.d.ts
import DataLoader from 'dataloader';
export import Options = DataLoader.Options; 
export as namespace DataLoader;

@fightingcat
Copy link

@MicahRamirez This is UMD module definition, which can be used as a module or through a global variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants