Search code examples
typescriptiife

Typescript: execute IIFE from UMD module that imports separate module


I have a tsconfig.json like this:

{
  "compilerOptions": {
    "lib": ["es2017", "dom"],
    "module": "umd",
    "outDir": "dist",
    "target": "es5",
    "declaration": true
  },
  "compileOnSave": true,
  "files": [
    "myClass.ts"
    "demo.ts"
  ]
}

My typescript, demo.ts:

import { MyClass } from './myClass';

(() => {
  console.log('fired IIFE');
  document.onreadystatechange = function () {
    if (document.readyState === 'interactive') {
      console.log('hello world', MyClass);
    }
  };
})();

Which gets compiled to:

(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./myClass"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var myClass_1 = require("./myClass");
    (function () {
        console.log('fired IIFE');
        document.onreadystatechange = function () {
            if (document.readyState === 'interactive') {
                console.log('hello world', myClass_1.MyClass);
            }
        };
    })();
});

My html:

<!DOCTYPE HTML>
<html>
<head>
  <title>Test</title>
  <script src="./dist/myClass.js"></script>
  <script src="./dist/demo.js"></script>
</head>
<body>
  <p>test</p>
</body>
</html>

I have also tried:

<!DOCTYPE HTML>
<html>
<head>
  <title>Test</title>
</head>
<body>
  <p>test</p>
  <script src="./dist/myClass.js"></script>
  <script src="./dist/demo.js"></script>
</body>
</html>

Neither way executes the script, and neither log statement is printed. What am I doing wrong?


Solution

  • Turns out you need to use requirejs in order to execute a typescript-generated UMD module:

    <!DOCTYPE HTML>
    <html>
    <head>
      <title>Test</title>
      <script data-main="dist/demo" type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.1/require.min.js"></script>
    </head>
    <body>
      <p>test</p>
    </body>
    </html>
    

    What's going on here is that data-main points to the .js file that loads everything else. You don't need to load your class script separately, require.js does that for you.

    This threw me because with gulp-wrap-umd package, the wrapper it provided would not create an isolate scope for the enclosed script, it was effectively global, whereas typescript generated UMD wrapped scripts have isolate scopes.

    In this implementation my bootstrapping script demo.js looks like this:

    (function (factory) {
      if (typeof module === "object" && typeof module.exports === "object") {
          var v = factory(require, exports);
          if (v !== undefined) module.exports = v;
      }
      else if (typeof define === "function" && define.amd) {
          define(["require", "exports", "./myClass"], factory);
      }
    })(function (require, exports) {
      "use strict";
      Object.defineProperty(exports, "__esModule", { value: true });
      require(["./dist/myClass"], (myClassModule) => {
    
        console.log('my class:', myClassModule.MyClass);
      });
    

    Where the log statement is, you can run any script without needing document.onready... or window.onload.