How do I import an IIFE-based JavaScript module into an Angular TypeScript app?
Asked Answered
S

2

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();
Sexagesimal answered 16/1, 2019 at 21:23 Comment(0)
C
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;
Cayser answered 16/1, 2019 at 21:57 Comment(1)
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.Sexagesimal
W
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.

Wayfarer answered 17/1, 2019 at 2:9 Comment(5)
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.Sexagesimal
@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.Wayfarer
It's an Angular 6/TypeScript front-end using webpack.Sexagesimal
@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.Wayfarer
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.Sexagesimal

© 2022 - 2024 — McMap. All rights reserved.