3

I have a library that is basically an IIFE that sets a global variable, and clients are supposed to operate on said variable. So, in module.js, I have something like

window.myModule = (function(){
    ...

    return {
        foo: foo,
        bar: bar
    }
})();

I want to make it compatible with ES6 modules, so that I could do

import * as theModule from 'module.js';

as well as

<script src="module.js"></script>

How can that be accomplished? I remember some libraries that were that way (even AMD-compatible) but I don't even know what to search for.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
user4052054
  • 395
  • 1
  • 6
  • 22

2 Answers2

2

ES6 modules, IMHO, were inspired by the value of IIFEs, encapsulation being an important benefit. So, refactoring an IIFE could be straightforward.

First you can remove the IIFE wrapper (you don't have to but, there's no benefit to keeping it, and you may have to be careful because the scope for the arguments you're passing in may differ).

If you know that the library is intended for the browser only AND you want to maintain backwards compatibility, then you can replace your root variable with window.

The next challenge is to identify the public API and export it. So, say that some of the original API looks like this:

root.MyLib.prototype.somePublicFn = function () {...}

You'd export this function like this

export let somePublicFn = function () {...}

And, when you do

import * as libFns from 'myLib'

libFns will act as a sort of namespace that will let you do,

libFns.somePublicFn(...)

in the importing module.

And, like I mentioned above, if you want to also make these exports available globally, you'd have to hand-wire this yourself and do something like

const api = {
  somePublicFn
  ...
}
root.MyLib.prototype = Object.assign(root.MyLib.prototype, api)
seebiscuit
  • 4,905
  • 5
  • 31
  • 47
0

You need to not conflate the code you write with the code you provide to others: the latter is a tools question.

Write ES modules, then use something like this to provide something to your users they can easily consume via the method of their choice: commonJS, AMD, global, their choice with no more work on your part other than a build pipeline step.

Remove the IIFE wrapper and export what you were assigning.

Note about webpack:

Webpack uses an... ES2015-ish syntax for modules that doesn't actually work with e.g. <script type="module">. Since webpack is almost an industry-standard at this point, I would be remiss to not mention it. Unfortunately, it doesn't play all that great with the setup above. As far as I know, that's still an open problem.

Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • I would like not to use another build step. I remember having checked some time ago a module that started with an `if`, checking if the environment supported ES6 modules, but I cannot remember what to do next. – user4052054 Feb 09 '19 at 01:20
  • @user4052054 that's literally not possible. ES 6 modules are statically linked. It's a syntax error to have an import or export statement inside a conditional (or anywhere other than the top level). There's a proposal for dynamic imports based on promises but it hasn't been standardized yet. It's build step or keep writing IIFEs. – Jared Smith Feb 09 '19 at 01:27