4

I'm playing around with some ES6 features using Chrome v65

I have the following files:

base.js :

export function consoleLogger(message) {
    console.log(message);
}

main.js

import { consoleLogger } from './base.js';
consoleLogger('Some message');

utils.js

function sayMyName(name) {
    console.log(name);
}

imports.html

<!DOCTYPE html>
<html>
<head>
    <script src='./main.js' type='module'></script>
    <script src='./utils.js'></script>
</head>
<body>
    <script>
        sayMyName('Cool name');
    </script>
</body>
</html>

Used like this everything seems fine, and in the console I get

Cool name utils.js:2

Some message base.js:2

However lets imagine scenario where I need some additional data in order to compose the message for the consoleLogger method. Then I would like to have something like this in main.js

function logToConsole(msg) {
    consoleLogger(msg);
}

and in imports.html

<script>
    sayMyName('Cool name');
    logToConsole('Send from log to console');
</script>

then for the logToConsole('Send from log to console'); call in my html file in the console I get:

Uncaught ReferenceError: logToConsole is not defined at imports.html:10

So there is no problem to import the consoleLogger from base.js and call it directly in main.js, there is no problem to include another .js file (utils.js) and call methods from there, but if I try to call a method declared in main.js which internally calls an imported method I got the error from above. As it seems it doesn't even matter if the method from main.js is referencing an imported method or not. I just commented everything and left only one simple method

main.js

import { consoleLogger } from './base.js';

/*function logToConsole(msg) {
    consoleLogger(msg);
}

consoleLogger('Some message');*/

function randomStuff() {
    console.log('random stuff');
}

and in the console I got this error:

imports.html:11 Uncaught ReferenceError: randomStuff is not defined at imports.html:11

Can someone explain me the reason for this behavior?

Leron
  • 9,546
  • 35
  • 156
  • 257
  • Since the document is parsed top-bottom (and JS is executed directly by default), maybe the problem is the order your files are included. – Seblor May 04 '18 at 13:36
  • @Seblor It seems unlikely for me since the reference tree is `Imports.html` -> `main.js` -> `base.js`. In the html file I include only `main.js` which internally is importing `base.js` I don't know if it's possible to change this order. – Leron May 04 '18 at 13:39
  • See also [How to call one function defined in a javascript to another javascript file?](https://stackoverflow.com/q/57699164/1048572) and [ES6 Modules: Undefined onclick function after import](https://stackoverflow.com/q/44590393/1048572) or [Use functions defined in ES6 module directly in html](https://stackoverflow.com/q/53630310/1048572) – Bergi May 26 '21 at 18:30

4 Answers4

4

One of the purposes of ES modules (and JS modules in general) is to prevent the pollution of global scope.

Module exports aren't supposed to leak to global scope. The use of modules usually assumes that all first-party code resides in modules. logToConsole('Send from log to console') goes to main module.

In case there's a need to interoperate with global scope, a variable should be explicitly exposed as a global inside a module:

window.logToConsole = function (msg) {
    consoleLogger(msg);
}

As another answer already mentions, there still may be race condition because modules are loaded asynchronously. Script should be postponed until the document will be ready - jQuery ready event or native counterpart:

<script>
document.addEventListener('DOMContentLoaded', () => {
    logToConsole('Send from log to console');
});
<script>
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Makes sense to me, but I still got an undefined error using this technique. – crenshaw-dev May 04 '18 at 13:53
  • @estus This makes a lot of sens. A little bit off topic, but you want to use modules to encapsulate logic, but at some point you need to provide some entry point. Like I think the logger is a good example where you may wnat to hide a lot of implementation logic but still being able to call some `Log` method. Then what is the pattern to do so? – Leron May 04 '18 at 13:54
  • 1
    I added the clarification. In your case `main.js` is entry point, so you may want to stick to modules all across the app. Yes, in some cases you may want to expose some things to global scope to be accessible in regular scripts. If it's the case, then you should do that like is shown in the answer, I guess. If race conditions are intolerable (e.g. you need to have logToConsole at the start of ), then this piece of code likely should go to regular script. Since native module implementations are immature, they are much less flexible than bundled modular environments like Webpack. – Estus Flask May 04 '18 at 14:03
1

That's because of this line -

<script src='./main.js' type='module'></script>

scripts with type="module" defer execution till their dependencies are met. And in the meanwhile. Other scripts will just execute.

Which means that your

<script>
    sayMyName('Cool name');
    logToConsole('Send from log to console');
</script>

will execute before the 'module script' main.js is resolved and evaluated.

Vandesh
  • 6,368
  • 1
  • 26
  • 38
1

The browser interprets logToConsole in imports.html as window.logToConsole, i.e. it expects that function to exist in the global namespace.

By default, objects in an es6 module are not placed in the global namespace when added to an HTML page.

You can access logToConsole by importing it explicitly in HTML:

<script type="module">
    import { logToConsole } from './main.js';

    sayMyName('Cool name');
    logToConsole('Got it');
</script>
crenshaw-dev
  • 7,504
  • 3
  • 45
  • 81
  • 1
    Do you have an idea is this a common approach to access methods like this? I try to figure out what is the standard approach to handle this. – Leron May 04 '18 at 14:03
  • 1
    I don't know, I'm just starting to learn es6 myself. I would suggest 1) avoid having JS in HTML and 2) try to handle everything in the context of es6 imports. This keeps everything clean and allows you to easily package your JS for the web in a way that supports older browsers (using something like webpack). – crenshaw-dev May 04 '18 at 14:14
0

you should export the function in main.js like this:

export function logToConsole(msg) {

and then you can import it using a

<script type="module"> inline script with an import inside

see https://jakearchibald.com/2017/es-modules-in-browsers/

would look like this:

<script type="module">
    import { logToConsole } from "./main.js";
    sayMyName('Cool name');
    logToConsole('Send from log to console');
</script>
Tiago Coelho
  • 5,023
  • 10
  • 17