Vaadin 14 - difference between `@JsModule` and `@JavaScript` in npm mode
Say you have a script such as
function test(val){
alert('Hi'+val);
}
and you want to call the function as UI.getCurrent().getPage().executeJs("test('User')");
.
You try to place the script into the frontend/
folder, then load the script via @JavaScript
, and it doesn’t work.
Unfortunately that’s the way things work; please read below on why is that and what can be done about it.
The reason is that the script has been loaded as module script; and since the test
function was not published,
it is internal in the module and nobody can access it.
See Flow bug #8285 and this Vaadin Forum question for more details.
Module Scripts versus Classic Scripts
When you read the javadoc on @JsModule
and @JavaScript
and/or read the
Storing and Loading Resources
documentation, it’s not clear what the difference between the two are, apart maybe from
the loading order.
The main difference is that @JsModule
always loads the script as the module script,
while @JavaScript
loads the script either as the module script (if the path to script is prefixed with ./
- then the script will be loaded from the frontend/
folder),
or as a classic script (if loading from the external URL such as https://
).
The difference between the module script and the classic script is summarized at the Classic scripts v/s module scripts in Javascript Stack Overflow Answer. Quoting:
- Modules are singleton. They will be loaded and executed only once.
- Modules can use import and export.
- Modules are always executed in strict mode.
- All objects (class, const, function, let or var) are private unless explicitly exported.
- The value of “this” is undefined at the outer scope (not window).
- Modules are loaded asynchronously.
- Modules are loaded using CORS. see Access-Control-Allow-Origin: *.
- Modules don’t send cookies and authentication info by default. See crossorigin=”use-credentials”.
- imports are resolved statically at load time rather than dynamically at runtime.
- html comments are not allowed.
You should always prefer js code which loads as the module script - you can find such libraries in the npmjs repository. However, certain old scripts won’t work as module scripts (most probably because of the strict mode); then you will need to load them as old scripts.
Loading classic scripts from your app
You currently can’t use an annotation-based approach to load a classic script locally from your app:
@HtmlImport
annotation is ignored in the npm mode - it does absolutely nothing. Keep that in mind and never use this annotation unless you’re also targeting Vaadin 14 compatibility mode.@JsModule
always loads the script as a module script;@JavaScript
always loads the script as a module script when loading the script locally. You can’t trick@JavaScript
to load the script “as external” from your WARsrc/main/webapp
using thecontext://
prefix since that’s broken: Flow bug #8290.
The only way to load a script as a classic script is to place the javascript file into
e.g. src/main/webapp/js/test.js
and call Page.addJavaScript("context://js/test.js")
.
Loading the script as module script
You can use both @JsModule
and @JavaScript
to load script as a module script.
You should always prefer @JsModule
over @JavaScript
when loading module scripts:
- the
@JavaScript
only loads stuff from thefrontend/
folder, while@JsModule
is able to load the script both fromfrontend/
andnode_modules/
folder; - also, the name
@JsModule
clearly states that the script is going to be loaded as a module script.
Certain scripts won’t work as module scripts though, because of strict mode. In such case you’ll have to load them as classic scripts.
Publishing the function to window
You can publish the function to the window
object as follows:
window.test = function test(val){
alert('Hi'+val);
}
Then the function should be callable via UI.getCurrent().getPage().executeJs("test('User')");
,
since the javascript snippet runs in the context of the window
object.
Exporting the function
An alternative approach would be to load the script as a module script and export the test
function as follows:
export function test(val){
alert('Hi'+val);
}
Unfortunately, such exported function is not callable via UI.getCurrent().getPage().executeJs("test('User')");
because the code snippet running via executeJs()
would have to import the module script first.
That is currently not possible, please see Flow bug #5094
for more details.
Exporting a module function to window
You can define a frontend/app.js
with this content:
import { MyThing } from './mything.js';
window.MyThing = MyThing;
Then import it via @JsModule
or @JavaScript
; now you can access window.MyThing
from anywhere.
Further Resources
JSLoader for Vaadin can easily also load JS (and CSS)
resources directly from Java classpath (e.g. src/main/resources
):
JSLoader.loadJavaResource(uiInstance, MyAddon.class, "myaddon","somescript.js", "somestyles.css");