21

I would like to create a project using Typescript modules, however allow it to be consumed from vanilla javascript.

Lets say it contains 3 modules each containing a single class, A, B, and C.

i.e.

A.ts:

export default class A {
    /* things */
}

B.ts:

export default class B {
    /* things */
}

C.ts:

export default class C {
    /* things */
}

All of these are built and bundled into one dist.js file with webpack. I would like the user of the library to be able to do something akin to

<script src="dist.js"></script>
<script>
    var foo = new LettersLibrary.A();
</script>

how would I go about doing this, ultimately the goal is to be able to develop taking advantage of typescript modules, but provide a library usable from vanilla js.

L. L. Blumire
  • 345
  • 1
  • 2
  • 9
  • you could consider avoiding any typescript custom module or namespacing since [`es6` brings modules natively](http://exploringjs.com/es6/ch_modules.html). Just be es compliant. – Hitmands Dec 14 '17 at 14:47

1 Answers1

15

Use a TypeScript Namespace for this. You can declare your classes inside it and then export them from inside the module. Your user will then be able to use it like you want.

https://www.typescriptlang.org/docs/handbook/namespaces.html

Example:

namespace LettersLibrary {
  export class A {
    hello = "Hello World";
  }

  export class B {
    myBool = false;
  }

  export class C {
    someInt = 42;
  }
}

In JavaScript, you would then do:

const instance = new LettersLibrary.A ();
console.log (instance.hello); // "Hello World"

If you need to re-export classes from other files, just export the imported class as const and type (useful for TypeScript development, otherwise you will not be able to use the type from the module):

import importA from "...";
import importB from "...";
import importC from "...";

namespace LettersLibrary {
    export const A = importA;
    export type A = importA;

    // Same for B and C
}

When using WebPack, you will have to export it as a library. For this, you can use the libraryExport configuration together with the library and libraryTarget options. See: https://webpack.js.org/configuration/output/#output-libraryexport

Thanks to @Ghabriel Nunes, who informed me that modules are now named namespaces.

NikxDa
  • 4,137
  • 1
  • 26
  • 48
  • Is this possible preserving the fact A, B, and C are in seperate files? If it is then this is the answer I was looking for! – L. L. Blumire Dec 14 '17 at 13:57
  • 1
    Sure. Just import the class you need and then use `export const A = A;` and `export type A = A;` combined to have a fully functional re-exported class. I've updated my answer. – NikxDa Dec 14 '17 at 13:58
  • On export const I get `'A' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.` and `Block-scoped variable 'A' used before its declaration.` Your edit solves this, thank you ^^ – L. L. Blumire Dec 14 '17 at 14:02
  • Sorry, you need to have a different name for imported classes, I missed that. See the updated post. – NikxDa Dec 14 '17 at 14:03
  • The `module` keyword is deprecated. Use `namespace` instead. – Ghabriel Nunes Dec 14 '17 at 14:05
  • 1
    @GhabrielNunes From what I know, this is not quite true. Internal modules are now namespaces, while external modules are just modules. At least this is what the official TypeScript site says. – NikxDa Dec 14 '17 at 14:06
  • Doing this results in `LettersLibrary is not defined.` from the JS side – L. L. Blumire Dec 14 '17 at 14:07
  • Works just fine for me, at least in the TypeScript playground. – NikxDa Dec 14 '17 at 14:08
  • Let me throw together an example with it not working and throw it up on github real quick and then hopefully you can see where I'm going wrong. – L. L. Blumire Dec 14 '17 at 14:09
  • Sure, I'll wait for it. – NikxDa Dec 14 '17 at 14:09
  • 4
    `external modules are just modules` means that you just `export` whatever classes/variables you want directly instead of wrapping them in a `namespace`. The `module` keyword itself is completely equivalent to `namespace`. See [here](https://fizzylogic.nl/2016/02/07/typescript-internal-vs-external-modules/) – Ghabriel Nunes Dec 14 '17 at 14:13
  • https://github.com/LLBlumire/typescript-javascript-interop opening `index.html` and checking console gives `LettersLibrary is not defined` – L. L. Blumire Dec 14 '17 at 14:18
  • Thanks for the information, in that case I'll swap `module` for `namespace`. @LucilleBlumire I'll take a look shortly – NikxDa Dec 14 '17 at 14:18
  • 1
    This is not a TypeScript, but rather a WebPack issue. You can configure WebPack to make your code available as a library, as described here: https://stackoverflow.com/questions/34357489/calling-webpacked-code-from-outside-html-script-tag – NikxDa Dec 14 '17 at 14:27
  • 1
    Adding `libraryTarget: 'var'` and `library: 'LettersLibrary` to the output allows me to access a `LettersLibrary' from javascript, but `A`, `B`, and `C` remain undefined. edit: It works, I just have to do LettersLibrary.LettersLibrary.A, I'm going to remove the namespace created per this answer and hopefully it works – L. L. Blumire Dec 14 '17 at 14:33
  • 1
    That is because WebPack will now create a new element `LettersLibrary` for you. You will have to change the TypeScript code a bit. Read up on this topic here: https://webpack.js.org/configuration/output/ Your entry point will have to export the class to WebPack. – NikxDa Dec 14 '17 at 14:36
  • Per my current understanding, that would mean having the entry point (which is itself the file with the LettersLibrary namespace in it) default export the namespace, however I don't think you can default export a namespace. – L. L. Blumire Dec 14 '17 at 14:46
  • The most simple way: Extend the file where `LettersLibrary` is defined by `export = LettersLibrary`. That will fix the problem. See: https://gist.github.com/NikxDa/7402ba2d827ff87fb520d6546a4b40d3 – NikxDa Dec 14 '17 at 14:46
  • 1
    I've added `libraryExport: 'LettersLibrary'` into the webpack config and this seems to have resolved it. I've updated the github --- would you like to edit your answer to include this change and then I'll accept it – L. L. Blumire Dec 14 '17 at 14:48
  • 1
    That is new to me, but pretty nice. I've updated my answer. – NikxDa Dec 14 '17 at 14:49