Trying to use GoJS library I get an "import declarations may only appear at top level of a module" error.
main.html
...
<script id = "code">
import {makeNodes} from "./make_nodes.js";
function init(){
makeNodes(model);
...
}
</script>
<body onload="init()">
...
make_nodes.js
export function makeNodes(model){
...
}
As the error says, you can't use import except in a module. Your code isn't in a module. To make it a module, use type="module" in the script tag:
<script id = "code" type = "module">
All modern browsers natively support modules (Obsolete ones like Internet Explorer don't, and never will.)
Despite that native support, though, most people using import/export syntax are still doing so via transpilers and/or bundlers (like Vite, Rollup, Webpack, Browserify, ...).
Once you're actually using a module, init will no longer be a global, but your <body onload="init()"> expects init to be a global. (This is one of several reason not to use onxyz-attribute-style event handlers.) To fix that, hook up the event in your code, removing the onload attribute from <body>:
function init(){
makeNodes(model);
// ...
}
window.addEventListener("load", init);
However, the load event you're using happens very, very late in the page load process, waiting for all images and other resources to finish loading. In most cases, you're better off not doing that. Modules are always deferred, so they don't run until the HTML of the page has been parsed into the DOM, which is usually all you need. (We used to have to put script tags at the very end of the body element instead, but then the defer attribute was added for scripts, and again all type="module" scripts are deferred automatically.)
Here's an exmaple — notice that the script type="module" is above the elements its code operates on, and yet it works just fine (because it's deferred):
<script type="module">
document.getElementById("btn").addEventListener("click", () => {
console.log("Button clicked");
});
</script>
<input type="button" id="btn" value="Click me">
That wouldn't work with a non-module script unless you added the defer attribute.
Related
I am trying to execute a function from within a script. It shows no error but also doesnt execute the function.
file.mjs
function test() {
console.log("test")
}
This is my html:
index.html
<head>
<script type="module" src="file.mjs">
</head>
<body>
<button type="button" id="Submit">Submit</button>
<script>
document.getElementById('Submit').addEventListener('click', test);
</script>
</body>
This worked fine using 2 separate scripts, to make it global I was using type="text/javascript".
But since my file.mjs is actually importing a library I had to set it as type="module" and the function wont execute. Is there a way to make the module scope global?
Before I go on, there's a big problem that needs addressing:
Your syntax is wrong. If you want to import a script, that tag must have the src attribute and nothing within the tag. If you want to execute an "inline" script, it should NOT have the src attribute, so what you should have is two script tags, one to import the module, the other to execute your inline code.
Now, I'm not sure what you mean by "global". To obtain true "globalness", you would have to define your variable in the global object (window or globalThis) like so:
// file.mjs
function test() { ... }
window["test"] = test;
Then in your regular script you could just do document.getElementById('Submit').addEventListener('click', test);.
That sort of completely destroys the purpose of JS modules though, as it's supposed to be an "import/export" model.
Ignoring the "global" part of your question: if you want to use modules for what they're worth, you would be importing and exporting them, and you would only (almost always) link a <script type="module"> in your HTML if it only imports and never exports (exporting would be useless). The reason you need type="module" is to tell the browser that you're operating on modules so it'll allow you to import/export. With that said, your code would look like this:
// file.mjs
export function test() { ... }
And in your HTML, you would only need one script tag (that goes right before the ending body tag):
<script type="module">
import { test } from "/path/to/file.mjs";
document.getElementById('Submit').addEventListener('click', test);
</script>
I hope that clears it up for you, and docs if you need them.
I've got two javascript files, screenfunction.js and map.js
In screenfunctions.js there's a function that I want to be able to start another function called "go" which is in map.js, is this possible?
This is what's written in the function in screenfunctions.js. The console obviously tells me that "go" is not defined.
if (doAnimation == 0){
go(function(){});
}
Yes, as long as the required function has already been included beforehand.
There are two ways to do this.
ECMAScript modules
Read more about ES modules on the MDN.
Use this if you are writing a server-side application with Node.js (in which case ignore index.html), or writing a client-side application for modern browsers. All major browsers have supported ES modules since ~2018, but IE does not support them (check Can I use here).
<!-- index.html -->
<html>
<head>
<!-- ... -->
<script type="module" src="./screenfunctions.js"></script>
<script type="module" src="./map.js"></script>
</head>
<!-- ... -->
</html>
// screenfunctions.js
export function go() {
console.log('Called go()');
// ...
}
// map.js
import { go } from './screenfunctions.js';
go();
Legacy global declaration
Use this if you are writing a client-side application which must work in older browsers. This approach is worse because the function is declared in the global scope. This could interfere with other code which is also included. This approach is also more prone to breaking because the dependency of the second script upon the first is not obvious at first glance.
<!-- index.html -->
<html>
<head>
<!-- ... -->
<script src="./screenfunctions.js"></script>
<script src="./map.js"></script>
</head>
<!-- ... -->
</html>
// screenfunctions.js
function go() {
console.log('Called go()');
// ...
}
// map.js
// `go()` already exists, because `./screenfunctions.js` was included earlier in the markup.
go();
Yes! You'll want to export the function you want to use. See this doc for more information and examples.
It is well known to everyone that using defer is an efficient way to minimize the loading time of a website.
In my project, I am using Vue (included in the header using defer) and in a circumstance, I want to use a component that is created by another person. When I try to do Vue.Component(...) in the body of the HTML, it says Vue is undefined. It seems that my script in the body is running before the external script has been loaded. Is there any way to fix this issue?
I tried to do document.onload, but the function itself is not working.
PS: Just to be clear, the external script is referring to my js file where Vue is defined and I am not talking about the third party library at all.
Instead of document.onload you need to use window.onload or document.body.onload.
But an even better way is to wait for the load event on <script> tag:
<html>
<head>
<script id="vue-script" src="vue.js" charset="utf-8" defer></script>
</head>
<body>
<script type="text/javascript">
function onVueLoaded() {
Vue.render();
}
if ('Vue' in window) {
onVueLoaded();
} else {
var script = document.getElementById('vue-script');
script.addEventListener('load', onVueLoaded);
script.addEventListener('error', () => console.warn('failed to load Vue.js'));
}
</script>
</body>
</html>
Here I also added a handler for the error event if you wanted to explicitly handle loading errors.
I have the following html code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/blazy/1.8.2/blazy.min.js" defer></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha256-8WqyJLuWKRBVhxXIL1jBDD7SDxU936oZkCnxQbWwJVw=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.9.0/js/lightbox.min.js" defer></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous" defer></script>
<!-- 26 dec flexslider js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer></script>
<script defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
</head>
<body>
</body>
</html>
I get an error, saying jQuery is not defined. Now even if I remove defer from my inline JS code, it says jQuery is undefined. For some reason I have to keep the jQuery plugins in the head and keep my JS code inline. My question is:
Why doesn't inline Javascript code get deferred when defer attribute is present on it?
Is there a way to imitate the defer behavior on my inline Javascript code? I can put that at the end of body tag if required.
The scripts with the defer attribute load in the order they are specified, but not before the document itself has been loaded. As defer has no effect on script tags unless they also have the src attribute, the first script that gets executed is your inline script. So at that time jQuery is not loaded yet.
You can solve this in at least two ways:
Put your inline script in a .js file and reference it with a src attribute (in addition to the defer attribute which you already had there), or
Let your inline script wait for the document and the deferred scripts to be loaded. The DOMContentLoaded event will fire when that has happened:
<script>
window.addEventListener('DOMContentLoaded', function() {
(function($) {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
})(jQuery);
});
</script>
NB: Notice that in the latter case $(document).ready(function() is not included any more, as that would wait for the same event (DOMContentLoaded). You could still include it like you had in your original code, but then jQuery would just execute the callback immediately, which makes no practical difference.
You can create a Base64 URL out of the script and put it into the src!
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer>
</script>
I built a quick test to see it in action.
You should see an alert with Hello world! last if defer is working:
<script defer>
alert('Why no defer?!?');
</script>
<!-- alert('Hello world!'); -->
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer></script>
<script>
alert('Buh-bye world!');
</script>
Doing it manually is a little laborious so if you have the luxury of compiling your HTML in some way (Handlebars, Angular, etc.) then that helps a lot.
I'm currently using:
<script src="data:text/javascript;base64,{{base64 "alert('Hello world!');"}}"
defer>
</script>
You can also use type="module":
<meta charset="utf-8">
<script type="module">
let t = document.getElementById('top');
console.log(t);
</script>
<h1 id="top">Top Questions</h1>
https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-type
From MDN docs:
defer
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded. The defer attribute should only be used on external scripts.
This is called an IIFE (Immediately Invoked Function Expression) which gets executed before DOM is available. So, in that case jQuery is undefined because it it not in the DOM.
defer loading with plain text Data URI - Chrome and FF
#noLib #vanillaJS
suggest not to use on Cross Browser PRODuction
until MS IE dies and MS Edge will adopt the Chromium open source ;)
the only way to defer script is external file or Data_URI (without using event DOMContentLoaded)
defer
spec script#attr-defer (MDN web docs): "This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.)"
Data_URI
spec Data_URI
with right type "text/javascript" there is no need to base64 at all... ;)
using plain text so you can use simple:
<script defer src="data:text/javascript,
//do something with b-lazy plugin, lightbox plugin and then with flexslider
lightbox.option({
resizeDuration: 200,
wrapAround: true
})
">
yes, it's little bit weird hack, but <script type="module"> are deferred by default, there is no other option to mix following in exact order:
module external files - deferred by default
module inline scripts - deferred by default
external files - optionally deferred
inline scripts - only with this hack - as I know (without libraries/frameworks)
Defer/async script tags are not good enough
There is a common knowledge that you should use <script src=".." async defer> (or set script.async = true before assigning src, when you do it from JS) and/or put your scripts at the very bottom of the page, so that as much as possible of the page gets loaded and rendered to the user, as fast as possible.
defer.js (note: I am the author of this script) is written in plain JavaScript, making lazy-loading other contents more fast and performant. You can defer any javascript files as well as inline script blocks efficiently.
If your page is just an HTML page enhanced with some JavaScript, then you're good with just <script async>. It takes time for browser to parse and execute those scripts, and each UI change may reflow your layout, make your load speed more slow, no one likes staring at a blank white page; users are impatient and will leave quickly.
In various cases, using async or defer does not deliver faster page speed than defer.js does.
I checked all the proposed solutions but all have their disadvantages. So I invented my own.
Put this inline script into your head tag or right after the start of body tag:
<script>var Defer = []; document.addEventListener('DOMContentLoaded', function() { while (Defer.length) Defer.shift().call(); }); </script>
This one liner will collect all the inline scripts you want to defer and run them respectively as soon as document is fully loaded. Now anytime you need to run an inline script deferred, just register it like:
<script>
alert('This alert will show immediately.');
Defer.push(function() {
alert('This alert will show only after document is loaded.');
// You can use anything which is not loaded yet, like jQuery
$(".selector").doSomeJqueryStuff();
});
// You can use it as many times as you like and in any place of your DOM.
Defer.push(function() {
// Any inline code you want to defer
});
</script>
This inline script will run only after document is loaded. That means you can run inline jQuery script having your jQuery stay at the end of your DOM.
You can use this data url as src attribute
data:application/javascript,eval(document.currentScript.textContent)
which takes this current script tag and evaluate its content as if it was inside an external file.
it also works with lazy attribute.
it uses document.currentScript which not supported by IE browsers.
<script defer src="https://cdn.jsdelivr.net/npm/vue"></script>
<script defer src="data:application/javascript,eval(document.currentScript.textContent)">
console.log('defered', typeof Vue); // function
</script>
<script>
console.log('not defered', typeof Vue); // undefined
</script>
There is a somewhat less obscure way to accomplish deferral that does not require callbacks, promises, or data urls ... although it does a little DOM manipulation in the background. The tiny library (109 bytes compressed/gziped) https://www.npmjs.com/package/deferscript let's you do this. The example below is based on the original post.
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer>
</script>
<script src="./deferscript.js" defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
All you have to do is insert a src attribute with the value ./deferscript.js.
If the problem is that jQuery variable $ is not defined, maybe you can create a fake $ function that returns a ready function waiting for the DOMContentLoaded?
All my inline scripts has $(document).ready(..... and the problem is that $ is not defined as the header scripts are deferred.
So, just add a fake $ in an inline script in head:
<script type="text/javascript">
var $ = function(element) {
return {
ready: function(callback) {
// in case the document is already rendered
if (document.readyState!="loading") callback();
// modern browsers
else if (document.addEventListener)
document.addEventListener("DOMContentLoaded", callback);
// IE <= 8
else document.attachEvent("onreadystatechange", function(){
if (document.readyState=="complete") callback();
});
}
};
};
</script>
I swear I have included jquery in the page header, it is right there!
Nonetheless the following code, which I've included near the bottom of the page (and inline for now) gives me an error saying "TypeError: $ is not a function."
<script>
function displayResult(longA, latA, longB, latB, units) {
$("#distance").html(calcDist(longA, latA, longB, latB, units));
if (units=="m") {
$("#unitLabel").html("miles");
$("units").prop("selectedIndex",0);
} else {
$("#unitLabel").html("kilometers");
$("#units").prop("selectedIndex",1);
}
$("#longA").val(longA);
$("#latA").val(latA);
$("#longB").val(longB);
$("#latB").val(latB);
}
$("#calculateButton").click(function() { //This is the line it's complaining about
var longA=$("#longA").val();
var latA=$("#latA").val();
var longB=$("#longB").val();
var latB=$("#latB").val();
var units=$("#units").val();
displayResult(longA, latA, longB, latB, units);
})(jQuery);
</script>
Higher up in the page header I've got the following:
<script src="jquery.js" ></script>
<script src="calcDistSinglePage.js" ></script>
I'm not using Wordpress or anything, this is a very straightforward hand-coded HTML page.
Try wrapping your code in a closure (which is considered good practice anyways):
(function($) {
$("#calculateButton").click(function() {
// do stuff...
});
}(jQuery));
If this snippet still complains with the same error, there's bound to be a problem with the way you're loading the jQuery library.
Also, make sure that you don't overwrite the $ variable in your other code. For example, inside calcDistSinglePage.js.
The dollar-sign is a very straight-forward javascript variable and can be reassigned to whatever you want. According to the error, $ currently is something but not a function (otherwise you'd receive a ReferenceError stating that $ is undefined). So probably, somewhere in your code, you've overwritten it.
Make sure the jQuery library it's the first script you load.
Add this just before the closing </body> tag.
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-{{JQUERY_VERSION}}.min.js"><\/script>')</script>
Download the file locally and inside js/vendor/ add the file.
Replace the value {{JQUERY_VERSION}} in the script above adding your jquery version.
Here one CDN you could use.
https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js
https://code.jquery.com/jquery-2.1.3.min.js
You are probably linking it from a root folder that is not your HTML folder. Use an absolute path:
<script src="/jquery.js" ></script>
Or make sure jquery.js is in the same folder as your HTML.