Load and consume legacy JS modules (e.g. IIFEs) via ES6 module imports
Asked Answered
S

2

11

I have IIFE functions for some of the library code in an legacy application that needs to work for IE10+ (No ES6 module loading, etc).

However, I am starting to develop an React app that will be using ES6 and TypeScript and I want to reuse the code I already have without duplicating the files. After a bit of research I found that I'd want to use a UMD pattern to allow these library files to work both as <script src=*> imports and to allow the React app to import them via ES6 module loading.

I came up with the following conversion:

var Utils = (function(){
  var self = {
    MyFunction: function(){
      console.log("MyFunction");
    }
  };
  return self;
})();

to

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (factory((global.Utils = {})));
}(this, (function (exports) { 
  exports.MyFunction = function(){
      console.log("MyFunction");
    };
})));

This will allow loading via Import Utils from './Utils.js' command and also allow it to be inserted using a script tag <script src='Utils.js'></script>

However, some of my IIFE use other IIFE's as a dependency (bad I know but a reality).

var Utils = Utils; // Used to indicate that there is dependency on Utils
var RandomHelper = (function(){
  var self = {
    DoThing: function(){
      Utils.MyFunction();
    }
  };
  return self;
})();

If correctly turn RandomHelper and Utils into files that can be imported, the React app isn't compatible with this technique. Doing simply

Import Utils from './Utils.js'
Import RandomHelper from './RandomHelper.js'

does not work because I believe Utils is not window scoped. It will load without issue but RandomHelper.DoThing() will throw that Utils is not defined.

In the legacy app

<script src='Utils.js'></script>
<script src='RandomHelper.js'></script>

works flawlessly.

How can I have RandomHelper be able to use Utils in a React app, keeping it IE and ES5 compatible but still work in react. Perhaps somehow setting a window/global variable?

PS: I understand the point of the ES6 module loading is to deal with dependencies and my existing IIFEs are not ideal. I plan to eventually switch es6 classes and better dependency control but for now I want to use whats available to be without re-writing

Santasantacruz answered 20/10, 2019 at 4:24 Comment(10)
React uses jsx and no browser understands jsx so you need babel anyway, there is no use to not use import statements in a react project because you have to use babel anyway. React is also moving away from OO so saying you want to use ES6 classes with react doesn't make much sense. It still supports classes but is moving towards functional components.Predispose
Yes I have babel/webpack and I use the CRA framework.Santasantacruz
In node.js I can also use global.Utils = (func... and var Utils = global.Utils; then.Mothball
Could rub some web component love on it with some stenciljs I imagine depending on what you need to support.Honey
I think you really should move to ES6 import syntax for everything that you want to use in your new app, and transpile it back into the IIFE (or simply UMD) format for the legacy application. You don't have to rewrite the complete file, but fix the dependency declarations.Psilomelane
Are you able to import Utils.js into RandomHelper.js either through import or require?Kohler
Yes I can and that does fix the dependency for the React app but breaks it for the legacy application. Adding React and or babel/transpiling for the legacy app is not an optionSantasantacruz
@Predispose React definitely does not require JSX, you can easily avoid it, as described in the doc: reactjs.org/docs/react-without-es6.htmlSeville
@Seville You posted the wrong link, that was about ES6, I was talking about jsx but if you want to write React code without jsx then go for it, I wish you good luck with your career because you're going to need it.Predispose
My mistake, it's right below it under react without jsx. I spent about 3 months with react without jsx. While it's not as readable, and takes longer to write, it's certainly very doable if the constraint is necessary. Saying you must use jsx with react is just not correct.Seville
E
6

Let's get this out of the way first, module features, if not explicitly exported, are privately scoped to the defining module. You can't get around this fact. But there are work-around options you may consider.

1. Assuming minimal modification of legacy code is acceptable

A work around with minimal changes to your legacy code would be to simply add Utils and RandomHelper to the window object. For instance, change var Utils = (...)(); to window.Utils = (...)();. Consequently, the object will be accessible from the global object by both legacy codes (loaded via import) and newer code base.

2. Assuming absolutely no modification in the legacy code can be tolerated

A new ES6 module should be created as proxy for loading the legacy scripts:

// ./legacy-main.js

const utilsScript = await fetch( './Utils.js' )
const randomHelperScript = await fetch( './RandomHelper.js' )

const utilsScriptText = await utilsScript.text()
const randomHelperScriptText = await randomHelperScript.text()

// Support access to `Utils` via `import` 
export const Utils = Function( `${utilsScriptText}; return Utils;` )()
// Additionally support access via global object 
Object.defineProperty(window, 'Utils', { value: Utils })

// Support access to `RandomHelper` via `import`
// Note that `Utils` which is a dependency for `RandomHelper` ought to be explicitly injected
// into the scope of execution of `RandomHelper`.
export const RandomHelper = Function( 'Utils', `${randomHelperScriptText}; return RandomHelper;` )( Utils )
// Additionally support access via global object 
Object.defineProperty(window, 'RandomHelper', { value: RandomHelper })

Finally, you may import Utils and RandomHelper from legacy-main.js when required:

import { Utils, RandomHelper } from './legacy-main.js'

Utils.MyFunction()
RandomHelper.DoThing()
Epifocal answered 29/10, 2019 at 20:46 Comment(0)
D
0

One approach you could consider is some form of dependency injection: have your React app receive RandomHelper, or some of it's properties, from the outside world. Then you can remove it when you're ready to cut the cord.

var Utils = (function(){
  var self = {
    MyFunction: function(name){
      return `Hello, ${name}!`;
    }
  };
  return self;
})();

var RandomHelper = (function(){
  var self = {
    DoThing: function(name){
      return Utils.MyFunction(name);
    }
  };
  return self;
})();

const ComponentOne = ({hello}) => {
  return <h1>{hello('ComponentOne')}</h1>;
}

const ComponentTwo = ({hello}) => {
  return <h2>{hello('ComponentTwo')}</h2>
}

const App = ({ExternalFunctions}) => {
  return (
    <header>
      <ComponentOne hello={ExternalFunctions.hello} />
      <ComponentTwo hello={ExternalFunctions.hello} />
    </header>
  )
}

ReactDOM.render(
  <App ExternalFunctions={{hello: RandomHelper.DoThing}} />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>
Daisie answered 30/10, 2019 at 0:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.