1

I'm a bit wondering, why nobody else had this problem or maybe I just don't use the right words to describe it. The problem is, that I have a module that I publish to npm that has to versions. One can be loaded through system-js and use directly from npm, the other version is a selfexecuting bundle that I create with system-js-builder.

Let's asume the module is called @company/foo I have a index.ts in the root folder, that simply exports everything from src, where I also have an index.ts where all submodules are exported. So the index.ts looks like this.

export * from "./src/";

And in my modules I want to use it I can simply use the following.

import { bar } from "@company/foo";

So far so easy. No I create a self executing bundle from my index and give it the global name foo so I would be able to call foo.bar() if I add the script into a page or concatenate with others. This also works great. But now I have the problem, that I have no idea how to create typings for this bundle. My idea was to do something like

declare namespace foo {
    export * from "./src/";
}

which I thought describes very well what the bundling does. But typescript doesn't like this. I also tried something with modules but nothing works. How can I describe the fact, that what is exported from my src barrel is prefixed with the namespace foo?

I hope it's clear what I want to achieve.

DaSch
  • 931
  • 1
  • 5
  • 20

2 Answers2

1

You need to add two fields in package.json.

  1. main -- to tell the module loader what is the module's entry point other than index.js.
  2. typings -- to set TypeScript definition file for the module.

For example, assuming we have a module @company/foo including following fields in package.json,

{
    "main": "lib/bundle.js",
    "typings": "index.d.ts"
}

Now in your tsconfig.json, you want to have moduleResolution set:

{
    "moduleResolution": "node"
}

When you import from @company/foo,

import { bar } from '@company/foo';

In your index.d.ts, you should have this line to declare bar:

declare function bar();

TypeScript will try to look for an exported symbol bar from definition in node_modules/@company/foo/index.d.ts.

Update:

Here is a full example of re-exporting individual functions/objects from a different module, and exporting the namespace. This file should be called index.d.ts or main.d.ts etc so it is recognized as ambient definition by TypeScript.

import * as another from './src/another';
declare namespace hello {
  function bar();

  interface ProgrammerIntf {
    work();
    walk();
    play();
  }

  class Programmer implements ProgrammerIntf {
    work();
    walk();
    play();
  }

  export import world = another.world;
}

export default hello;

To use this namespace, in the caller script,

import hello from '@company/foo';
hello.bar();
hello.world();
let programmer = new hello.Programmer();

Update 2:

I found a way to do it in TypeScript's documentation that I have not noticed before.

You can declare all your types in the global scope as below:

import * as another from './src/another';

declare global {
  namespace hello {
    function bar();

    interface ProgrammerIntf {
      work();
      walk();
      play();
    }

    class Programmer implements ProgrammerIntf {
      work();
      walk();
      play();
    }

    export import world = another.world;
  }
}

Then in the caller script, just use:

import '@company/foo';
hello.bar();
hello.world();
let programmer = new hello.Programmer();

Of course if you bundle the declaration at the beginning of your bundle, you should be able to use hello directly without the import statement.

yuxhuang
  • 5,279
  • 1
  • 18
  • 13
  • But I'm not importing it the way you described in my mixed TypeScript/JavaScript Environment where I use namespaces. I need a declaration prefixed with foo from global. The bundle I created is loaded to global and I'm using it from there. The solution you're mentioning is for a TypeScript only environment for which I already have a working solution. – DaSch Dec 02 '16 at 16:56
  • @DaSch How is your mixed TypeScript/JavaScript environment? In that case can your TypeScript ambient declaration created as an `interface` and exported via `export default`? How complex is your source code? – yuxhuang Dec 02 '16 at 18:59
  • the mixed TypeScript/JavaScript environment is huge and very complex. There is nothing about that I can change. That's why I need to create the correct typings for my bundle so I can use it there from global. – DaSch Dec 05 '16 at 11:25
  • @DaSch TypeScript (2.0) currently doesn't allow re-exporting modules in a different namespace. So I think the second best approach is using ambient declarations like what jQuery or d3.js do. Just define interfaces and export a default variable in the namespace. I am updating the answer to reflect this. – yuxhuang Dec 06 '16 at 18:52
  • Well but that means that the process of bundling with systemjs builder to a self executing bundle can't be reflected in typings? That's really sad :( – DaSch Dec 07 '16 at 10:22
  • @DaSch found a way to do it. Please check my latest update. – yuxhuang Dec 08 '16 at 19:34
0

If I understand correct, you want a global variable foo which has all the exported members of the module.

You can achieve that by declare var and typeof.

import * as _foo from './src'
declare var foo: typeof _foo
// implement self-executing logic

Once you import the self executing code, a global variable foo will be available to all files in the project being compiled.

For JavaScript files, they are not typing aware so as long as you implemented logic you can access foo.

Herrington Darkholme
  • 5,979
  • 1
  • 27
  • 43
  • Well, that's really close. I don't get what you mean with self-executing logic. I now can import the module to my augmentation files, but it still isn't available in global. And importing the module to my augmentations make the other interfaces and declaration unavailable to global. – DaSch Dec 08 '16 at 16:27