Can I mix in javascript modules with "regular" javascript?
Asked Answered
L

4

8

I have a page setup which references a couple of javascript files and has some javascript inline in the page, which is generated when the page is loaded.

One of the javascript files contains a class from a third-party library.

The other javascript file contains various variables, event handlers and functions relating to the page, which use the class from the third-party library (var v = new thirdPartyClass(parameters);).

The third party has recently updated their library and switched to using modules, so now instead of having a class available, they have a class export available. This is my first exposure to js modules.

So now instead of including the js file, as I understand it, I have to import the class from the module. When I try that, I get a console error that only modules can import. I added type="module" to the <script> tag for my js file (it was already added to the third party js script tag) and then I could import their class, but now my functions can't be accessed from the page (function Uncaught ReferenceError: myFunction is not defined). I don't know if events work because it doesn't get that far any more.

Is there any way I can use their new exported class without having to completely restructure my js file to be a module and change the page to work with it?

Ladyfinger answered 3/11, 2020 at 15:9 Comment(2)
It seems the easiest solution would be to transpile their module into a regular script that puts the export in a global variable, e.g. UMD format. Then just use that and you don't have to change anything else.Paoting
@Paoting - Do you have any recommendations for tutorials on how to do that please? This is all new to me, I'm mostly a backend developer.Ladyfinger
Q
2

You can't create global variables in modules using var. To access the function you can export it out of the file:

export myFunction;

and import it into the file you need it in:

import {myFunction} from "./relativeFilePath";

Or if you want to create a global variable from within the module you can put the variable directly into the window object:

window.myFunction = function() {
  //code
};
Quarterstaff answered 3/11, 2020 at 15:16 Comment(4)
Using window.myFunction is useful to know, but this still means I have to put all my variables somewhere else and do this with all of the functions that are referenced in other places and re-test everything. I was hoping that there was a way to more simply replace var v = new thirdPartyClass(parameters) without having to make changes all over the place.Ladyfinger
If you want v to be global just change it to window.v = new thirdPartyClass(parameters);Quarterstaff
The problem isn't v by itself, the problem is that there are many functions and variables that are in my js file and I was hoping to avoid turning it into a module if possible. The thing is, if it's not a module, I can't figure out how to use thirdPartyClass from in that js file.Ladyfinger
You can use a module file to create v and make it global, then inlude the regular scripts. Not all scripts have to be modules.Quarterstaff
S
6

You can create a script type module, import the objects desired and put them in a window.{the name of your obj} as:

<script type="module">
    import { theItem } from "/path/to/module.js";
    window.theItem = theItem;
</script>

The remaining code can be in regular js.

Seifert answered 12/7, 2021 at 12:29 Comment(0)
Q
2

You can't create global variables in modules using var. To access the function you can export it out of the file:

export myFunction;

and import it into the file you need it in:

import {myFunction} from "./relativeFilePath";

Or if you want to create a global variable from within the module you can put the variable directly into the window object:

window.myFunction = function() {
  //code
};
Quarterstaff answered 3/11, 2020 at 15:16 Comment(4)
Using window.myFunction is useful to know, but this still means I have to put all my variables somewhere else and do this with all of the functions that are referenced in other places and re-test everything. I was hoping that there was a way to more simply replace var v = new thirdPartyClass(parameters) without having to make changes all over the place.Ladyfinger
If you want v to be global just change it to window.v = new thirdPartyClass(parameters);Quarterstaff
The problem isn't v by itself, the problem is that there are many functions and variables that are in my js file and I was hoping to avoid turning it into a module if possible. The thing is, if it's not a module, I can't figure out how to use thirdPartyClass from in that js file.Ladyfinger
You can use a module file to create v and make it global, then inlude the regular scripts. Not all scripts have to be modules.Quarterstaff
T
0

So now instead of including the js file, as I understand it, I have to import the class from the module. When I try that, I get a console error that only modules can import.

That's true for conventional, static import statements.

But, significantly, the dynamic import() operator (an alternative, aynschronous way to import modules) may be used to import modules into classic javascript files as well as into other modules.

MDN notes:

The import() syntax, commonly called dynamic import, is a function-like expression that allows loading an ECMAScript module asynchronously and dynamically into a potentially non-module environment [...]

and:

[...] you might need to use dynamic import:

  • When you are in a non-module environment (for example [...] a script file).

Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import


Example:

// /my-classic-javascript-file.js

const myAsyncFunction = async () => {
  const { myImportedClass } = await import('/path/to/module.js');
}

Further Reading:

Turpin answered 2/5, 2023 at 11:12 Comment(0)
R
0

A window property setter solution:

<script type="module">
   import Dms from 'https://cdn.jsdelivr.net/npm/[email protected]/dms.js';                 
   alert(Dms.toLat(-3.62, 'dms'));
   window.Dms = Dms;
</script>

<script>
  let Dms;
  Object.defineProperty(window, 'Dms', {
    get(){
      return Dms;
    },
    set(val){
      Dms = val;
      window.dispatchEvent(new Event('dms-set'));
    }
  });
  
  window.addEventListener('dms-set', () => alert('SECOND TIME: ' + Dms.toLat(-3.62, 'dms')));
  
</script>

Some promise solution:

<script type="module">
   import Dms from 'https://cdn.jsdelivr.net/npm/[email protected]/dms.js';                 
   alert(Dms.toLat(-3.62, 'dms'));
   window.Dms = Dms;
</script>

<script>

  promisifyGlobal('Dms');
  
  Dms.then(Dms => alert('SECOND TIME: ' + Dms.toLat(-3.62, 'dms')));

  function promisifyGlobal(name){
    let global, promise = new Promise(resolve => {
      Object.defineProperty(window, name, {
        get(){
          return promise;
        },
        set(val){
          global = val;
          resolve(global);
        }
      });
    });
  }

</script>

You can easily import dynamically into a non-module script:

<script>

  (async () => {
    
    const {default: Dms} = await import('https://cdn.jsdelivr.net/npm/[email protected]/dms.js');
    alert(Dms.toLat(-3.62, 'dms'));
  
  })();

</script>

And finally some crazy generic solution:

<script type="module">

  import Dms from 'https://cdn.jsdelivr.net/npm/[email protected]/dms.js'; 
  alert(Dms.toLat(-3.62, 'dms'));

</script>

<script>

  // this script will handle all imports 
  // tweak to import several exports and named exports

  for(const script of document.querySelectorAll('script[type=module]')) {
    
    const regex = /import\s+([^\s]+)\s+from\s+["\']([^"\']+)/g;
    let matches;
    while(matches = regex.exec(script.textContent)){
      const [, name, url] = matches;
      const src = `import ${name} from '${url}'; export default ${name};`;
      window[name] = new Promise(resolve => {
        import(`data:text/javascript, ${src}`).then(({default:imported}) => {
          setTimeout(() => resolve(imported));
        });
      });
    }

  }

</script>

<script>

  Dms.then(Dms => alert('SECOND TIME: ' + Dms.toLat(-3.62, 'dms')));

</script>
Rooster answered 17/6, 2023 at 1:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.