14

So I have a third-party SDK written as an oldschool IIFE based module. In other words it looks something like this:

var ThirdPartySDK = (function() {
  var export = {};

  // Add some methods to export

  return export;
})();

You would then be expected to use it by referencing it on the global scope like this:

<html>
  <body>
    <script src="lib/ThirdPartySDK.js">
    <script>
      ThirdPartySDK.foo();
    <\script>
  <\body>
<\html>

I could still use it this way of course, but is that really the best practice with Angular and TypeScript? Is there some way to set things up with angular/TypeScript/webpack so that I can use a proper import statement? Something like this:

import { ThirdPartySDK } from '../lib/ThirdPartySDK.js';
ThirdPartySDK.foo();
Zac Delventhal
  • 3,543
  • 3
  • 20
  • 26

2 Answers2

6

The best way to have a proper import statement for the actual value of ThirdPartySDK is to refactor the script to a module that exports this value. The following snippet allows you to use the import statement as showed:

export const ThirdPartySDK = {
    foo() { console.log('Doing foo'); }
};

For big libraries refactoring is not always that easy, so I see 2 approaches that do not involve too much refactoring:


1. Export the ThirdPartySDK variable

You could simply make a module out of the IIFE file by exporting the current IThirdPartySDK variable (returned by the IIFE), and then import it as you showed:

export const ThirdPartySDK = (function() {
    var _export = {};

    // Add some methods to export

    return _export;
})();

Note that if you want to have some useful information about the shape of ThirdPartySDK you would have to add a type annotation to the const declaration, and if SomeType (see below) does not yet exist you'll have to write it yourself:

export const ThirdPartySDK: SomeType = (function() {
// ...

At this point Typescript will start to complain about the IIFE expression not being assignable to SomeType; the quick 'solution' to tell typescript to pretend the expression evaluates to a value of type SomeType using the as keyword:

export const ThirdPartySDK: SomeType = (function() {
    // ...
})() as SomeType;

2. Keep the <script> tag and declare the variable

Another option it to keep the script tag, import nothing, and declare the variable and its expected type in typescript:

(But also in this case you might have to provide type definitions yourself)

interface SomeType {
    // SDK type shape goes here...
}

declare const ThirdPartySDK: SomeType;
JJWesterkamp
  • 7,559
  • 1
  • 22
  • 28
  • 2
    Thanks! I am a little reticent to modify the source file with an export statement, because I want to be able to just drop in a new version if/when it comes. The declaration might be the only way to go, but I was hoping there was something clever I could do like isolate/export the IIFE's global scope in a separate module or something. – Zac Delventhal Jan 16 '19 at 23:05
2

You can wrap the third-party SDK in a TypeScript module using a hack with eval.

Let's say that ThirdPartySDK.js looks like this:

var ThirdPartySDK = (function () {
    var exports = {
        foo: function () { console.log("Hello, world!"); }
    };
    return exports;
})();

You would then create a ThirdPartySDK-wrapper.ts module that looks something like this:

import * as fs from 'fs';
const script = fs.readFileSync('../lib/ThirdPartySDK.js').toString();

global.eval(script);

//@ts-ignore
export default ThirdPartySDK;

The @ts-ignore directive is required to keep the TypeScript compiler from complaining about not finding a declaration for the ThirdPartySDK variable (it is declared in the script executed through eval).

You can then import ThirdPartySDK through the wrapper module:

import ThirdPartySDK from './wrapper';
ThirdPartySDK.foo();  // Console output: "Hello, world!" 

Note that this wrapper only works for applications running in Node.js, since it uses fs.fileReadSync to get the contents of the script.

If you're going to use it in a browser, you will need some other way to retrieve the script. You could probably use frameworks such as WebPack to bundle the ThirdPartySDK script as a string value that you can require in the wrapper module.

mbj
  • 987
  • 5
  • 18
  • In the browser unfortunately. I'm a little surprised there is no standard solution for this considering how many IIFE modules used to be kicking around. – Zac Delventhal Jan 17 '19 at 03:53
  • @ZacDelventhal: Okay, so which module loader are you using? It shouldn't be too hard to adapt this approach to also work in the browser. – mbj Jan 17 '19 at 04:04
  • It's an Angular 6/TypeScript front-end using webpack. – Zac Delventhal Jan 17 '19 at 15:26
  • 1
    @ZacDelventhal: I haven't played around with Angular or Webpack in a long time (it was Angular 2.0 back then), but I could try experimenting a little to find out the details for how to make my approach work there, if that's what it's going to take for you to accept my answer :-) In the meantime, you might want to edit the question to include this requirement and also add the `webpack` tag. – mbj Jan 17 '19 at 15:50
  • 1
    Haha. I like your answer, but I don't think it is ideal, at least given my tool chain. I am playing around with an angular/webpack solution now, and will post it if I get it working. Took your advice and updated the tags/title/description to reflect the whole tool chain though. – Zac Delventhal Jan 17 '19 at 17:30