I am trying to accomplish a pretty simple thing: I have some code on a javascript module file and I import it on another javascript file (that doesn't export anything) and I want to call some of the defined functions in that file from the HTML directly.
Let's se some representative and minimal example of what's happening to me (actually tested the code and gives the exact same issue I'm experiencing with the real one, which is not really much more complex than this one):
module.js:
const mod = () => 'Hello there!';
export { mod };
main.js:
import { mod } from './module.js';
function hello()
{
console.log(mod());
}
main.html:
<!DOCTYPE html>
<html>
<head>
<script type="module" src="module.js"></script>
<script type="module" src="main.js"></script>
</head>
<body>
<button name="next-button" onclick="hello()">Obi-Wan abandons the high ground to salute you</button>
</body>
</html>
Without the import (and putting all the function definitions on a single .js file) I can call the function directly from the HTML. However, once I have introduced the modules I am no longer able to: it simply says that the "hello()" function is not defined.
I am completely new to ES6 modules (and to front-end javascript in fact), so I am fully aware all I just said is just lack of knowledge (or understanding), but I would appreciate any comment on what am I doing wrong and how to solve it so I can have my code in different files and be able to use it. Thank you all in advance!
Each module has its own scope. They are not sharing the global scope like "normal" scripts do. That means hello is only accessible inside the main.js module/file itself.
If you explicitly want to create a global variable, you can achieve that by creating a property on the global object, window:
function hello()
{
console.log(mod());
}
window.hello = hello;
See also Define global variable in a JavaScript function
Having said that, it's good practice to avoid global variables. Instead you can restructure the HTML to load the modules after the button was created and bind the event handler via JavaScript:
<!DOCTYPE html>
<html>
<body>
<button name="next-button">Obi-Wan abandons the high ground to salute you</button>
<script type="module" src="module.js"></script>
<script type="module" src="main.js"></script>
</body>
</html>
and
import { mod } from './module.js';
function hello()
{
console.log(mod());
}
document.querySelector('button').addEventListener('click', hello);
Related
Is there any way that use a function declared inside <script type="module"> from <script type="text/javascript>?
For example,
<script type="module">
function do_something(){ ... };
</script>
<script type="text/javascript">
do_something();
</script>
This is bad practice
You can explicitly make a variable a global, but modules appear to be loaded asynchronously so you need to wait until the module has been evaluated.
I'm using DOMContentLoaded here, but I don't know if that is reliable.
<script type="module">
function do_something() { console.log("Something"); } window.do_something = do_something;
</script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', (event) => {
do_something();
})
</script>
Instead of doing that, you should design your JS to not need globals in the first place.
Use a module as the entry point into your program, not a non-module.
Short answer, no (unless you wish to pollute the global namespace).
MDN Guide on modules states this clearly.
Last but not least, let's make this clear — module features are imported into the scope of a single script — they aren't available in the global scope. Therefore, you will only be able to access imported features in the script they are imported into, and you won't be able to access them from the JavaScript console, for example. You'll still get syntax errors shown in the DevTools, but you'll not be able to use some of the debugging techniques you might have expected to use.
I am testing ES6 Modules and want to let the user access some imported functions using onclick:
test.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Module Test</title>
</head>
<body>
<input type="button" value="click me" onclick="hello();"/>
<script type="module">import {hello} from "./test.js";</script>
</body>
</html>
test.js:
export function hello() {console.log("hello");}
When I click the button, the developer console says: ReferenceError: hello is not defined. How can I import functions from modules so that they are available as onclick functions?
I am using Firefox 54.0 with dom.moduleScripts.enabled set to true.
Module creates a scope to avoid name collisions.
Either expose your function to window object
import {hello} from './test.js'
window.hello = hello
Or use addEventListener to bind handler. Demo
<button type="button" id="hello">Click Me</button>
<script type="module">
import {hello} from './test.js'
document.querySelector('#hello').addEventListener('click', hello)
</script>
Module scope in ES6 modules:
When you import a script in the following manner using type="module":
<script type="module">import {hello} from "./test.js";</script>
You are creating a certain scope called module scope. Here is where modules scope is relative to other levels of scope. Starting from global they are:
Global scope: All declarations outside a module on the outermost level (i.e. not in a function, and for const and let not in a block) are accessible from everywhere in the Javascript runtime environment
Module scope: All declarations inside a module are scoped to the module. When other javascipt tries to access these declarations it will throw a reference error.
Function scope: All the variables declared inside (the top level of) a function body are function scoped
Block scope: let and const variables are block scoped
You were getting the referenceError because the hello() function was declared in the module, which was module scoped. As we saw earlier declarations inside module scope are only available within that module, and you tried to use it ouside the module.
We can make declarations inside a module global when we explicitly put it on the window object so we can use it outside of the module. For example:
window.hello = hello; // putting the hello function as a property on the window object
While the accepted answer is correct, it scales poorly once you start importing from multiple modules, or declaring multiple functions . Plus, as noted by #Quentin, it risks global name space pollution.
I prefer a slight modification
import { doThis, doThat } from './doStuff.js'
import { foo1, foo2 } from './foo.js'
function yetAnotherFunction() { ... }
window._allTheFns = { doThis, doThat, foo1, foo2, yetAnotherFunction }
// or, if you prefer, can subdivide
window._doStuffjs = { doThis, doThat }
window._foojs = { foo1, foo2 }
Uses an "unusual" property name to (hopefully) avoid global namespace issues
No need to immediately attach (or forget to attach) an EventListener.
You can even put the listener in the HTML, e.g. <button onclick="window._foojs.foo1(this)">Click Me</button>
As of 2022 I don't know of any other solution than the provided, except that you don't need to use window, but you can use globalThis
I don't know if this makes it any better.
advantages:
it is a tick more readable
you don't get a compiler error when checkJS is on (attribute "..." is not found on window etc...)
I'm even using LitHtml where you have #event notation, looks like this:
<element #click="${(e)=>console.log(e)}"></element>
but sadly there is no clean way to actually get a reference of the object that has the listener attached. the target, originalTarget and explicitOrOriginalTarget can be anything in the bubbling queue, which is super annoying and super confusing, but if you don't need that, LitHTML would be the way to go.
I am curious as to why I do not have access to defined variables in the browser console when I have my script type set to type="module".
Below is a hypothetical set up:
<!DOCTYPE html>
<html>
<head>
<div id="containerFirst">...</div>
<div id="differentContainer">...</div>
</head>
<body>
...
</body>
<script type="module" src="module.js"></script>
<script src="normal.js"></script>
</html>
And here are the two JS files, first module.js:
export const firstContainer = document.getElementById('containerFirst');
and similar variable structure in normal.js:
const otherContainer = document.getElementById('differentContainer');
When I run this in a browser, I can access the variable defined in normal.js by typing it directly into the console but not the one from module.js. I'm hoping to find some clarity on this matter.
Thanks!
You'd be able to access the module variables if you were paused on a breakpoint in the relevant module's code, but not if you aren't. Top-level declarations in modules aren't globals, by design. Each module gets its own scope, kind of like they're a function that gets called just once (that's a rough analogy), so top-level declarations are private to the module's scope (unless you export them, of course).
I am pretty new to JavaScript, and I was experimenting with the global scope following a tutorial.
In this tutorial I have 3 files:
1) index.htm:
<html>
<head></head>
<body>
<h1>HELLO WORLD !!!</h1>
<script src="app.js"></script>
<script src="utility.js"></script>
</body>
</html>
As you can see I include 2 JavaScript files.
2) app.js:
var person = 'Torny'; // When I call the logPerson() function this line override the person variable value into the global stack
//console.log(person);
logPerson();
3) utility.js in which is defined the logPerson() function defined into the app.js file:
var person = 'Steve';
function logPerson() {
console.log(person);
}
Ok, so I expected the following behavior:
Into the app.js file set the string 'Tony' as value of the person variable, the call the logPerson() function declared into the utility.js file and so print 'Tony' in the console.
The problem is that when I try to open the index.htm file into FireFox, I go into FireBug console and instead the 'Tony' value I obtain this error message:
ReferenceError: logPerson is not defined
So it seems that from the app.js file it can't see the logPerson() function that is defined into the utility.js file and imported.
Why? What am I missing? what is wrong?
In javascript the order of the files matters. You need to load the script which defines your function before you can use it. Switch the 2 <script> tags
<body>
<h1>HELLO WORLD !!!</h1>
<script src="utility.js"></script>
<script src="app.js"></script>
</body>
</html>
change
<script src="app.js"></script>
<script src="utility.js"></script>
to
<script src="utility.js"></script>
<script src="app.js"></script>
;) You try to get definitions from a script which is loaded later
greetings
First browser will parse the JS files.
If you see the order of script included
app.js
utility.js
So when it is parsing app.js. It sees logPerson() which is a function call not function definition. At this point of time the file utility.js is not parsed and the browser is not aware of that function also. (meaning till it reads utility.js).
So to solve this, call the logPerson api after dom-loaded, usiing body onload function.
The explanation is as simple as that Javascript code is interpreted on the fly: As one script is loaded, it is executed immediately.
So, you just have to declare the scripts in the right order of dependence.
You run the script which tries to use logPerson before you run the script that defined logPerson.
If you reverse the order of the scripts, it will work.
Function declaration hoisting only works within a given script.
In my case, it wasn't the <script> ordering that was the issue (as initially thought), but the scope in which the function was defined. To enable access of the function in other JS files, I made it a property of the object that is in the scope of those files. So now, it can be accessed as MyObject.myFunction wherever MyObject is instantiated.
(function ($) {
function MyObject(data) {
var myFunction = function(){
console.log('');
}
$.extend(this, {
// methods
"myFunction": myFunction,
});
}
}(jQuery));
I'm currently messing around with JavaScript to try and understand the language more. I want to make two different modules, one with generic helper functions and the other with specific functions to the problem.
How can I access functions from one module to another?
You have two options here. Both are fairly popular, so it's up to you which you choose.
The first is to define your helper module in the scope of your application module's parent:
var helpMod = (function(){
return {foo:"bar"}
})();
var appMod = (function(){
console.log(helpMod.foo);
})()
And the second is to directly import the module as a parameter to the closure function:
var helpMod = (function(){
return {foo:"bar"}
})();
var appMod = (function(h){
console.log(h.foo);
})(helpMod);
Direct imports are more explicit, but taking advantage of scoping can be easier - so long as you're comfortable with that variable in the global scope!
You would simply place the various functions into two separate files then reference them in a "sandbox" HTML page as follows:
helper.js
function helper_function() {
alert("this is a helper function");
}
specific.js
function specific_function() {
alert("this is a specific function");
}
index.html
<html>
<head>
<script src="helper.js"></script>
<script src="specific.js"></script>
</head>
<body>
<script type="text/javascript">
helper_function();
specific_function();
</script>
</body>
</html>