Vaadin 14 - how to use jQuery Plugins?

In Bower mode this was simple:

@JavaScript("/webjars/jquery/dist/jquery.min.js")
@JavaScript("/webjars/some-jquery-plugin/dist/plugin.min.js")

But in NPM mode the @JavaScript annotation breaks backwards compatibility by changing its behavior to not put those JavaScript declarations on the HTML page, but to pack it all up with webpack and scope the variables. However, jQuery plugins usually expect jQuery to be in an accessible scope. They often initialize themselves like so:

jQuery.somePlugin = {
  ...
}

This begs the question how to solve this dilemma? There is a thread that suggests several solutions to the general problem:

https://vaadin.com/forum/thread/17832455/how-to-use-external-javascript-libraries-correct-in-vaadin-14-npm

In short, it’s either “use a CDN” or include the Javascript programmatically in Java code, which is not a particularly elegant solution and loses all the benefits from a webpack based frontend build (which was the reason to switch to NPM mode in the first place).

I don’t think we can change the code of either jQuery or its plugins. The question is can Vaadin support the jQuery plugin ecosystem in an NPM based frontend build?

With webpack proper I believe it would simply be a question of how the frontend build is configured. But I think with Vaadin we are not supposed to hack the webpack build and do our own stuff there, are we?

You can find an example with jquery and a jquery-plugin.

Here the commit:
https://github.com/jcgueriaud1/vaadin-tooltip-js/commit/c2d5e0dee899f52c68f9f762a92333576746ad4e

You may have problems with your jquery-plugin and webpack, I tried some jquery-plugins and it’s not working very well with webpack. You may also have problem if you need to import css from the jquery-plugin.

Thank you very much for the pointer. Unfortunately the trick with the jquery-loader.js script does not work in my case. Here’s my code (Trumbowyg is the jQuery Plugin):

@NpmPackage(value = "jquery", version = "3.4.1")
@JsModule("./src/jquery-loader.js")
@JsModule("trumbowyg/dist/trumbowyg.min.js")

The error I am getting as a popup is: (TypeError) : window.Vaadin.Flow.selectConnector is undefined. And in the console there is the usual “jQuery is not defined” error. If I change my code to this:

@NpmPackage(value = "jquery", version = "3.4.1")
@JavaScript("./src/jquery-loader.js")
@JavaScript("trumbowyg/dist/trumbowyg.min.js")

then the popup error disappears, but I am still stuck with the “jQuery is not defined” error. If I remove the Trumbowyg declaration, then the jquery-loader.js is actually executed (I’ve put a console.log statement in there). But with the Trumbowyg declaration I never see the console.log statement, which means to me that the jquery-loader.js never got executed. Apparently webpack builds the package in a way that Trumbowyg is executed first.

Hi,

Did you add npm annotation for trumbowyg? Do you have any logs? (error)
JSModule annotation should be the correct annotation and you may have error in the js console or in the Java console (when webpack is running).

Yes, I did add the npm annotation for Trumbowyg. But, if I am not mistaken, that is not essential. All it does is download Trumbowyg and put it in node_modules. So as long as I have Trumbowyg in node_modules (from a previous download or such) I don’t need the npm annotation to reproduce the error. In any case, if Trumbowyg wasn’t there, then the error could not occur, because it occurs when Trumbowyg is loaded and its init function tries to find jQuery.

Apart from the errors in the console that I listed above there are no error messages. Nothing in the server logs during build or run.

Meanwhile I’ve tested whether my app would work if I included all JavaScript from CDN. And indeed, the error then disappears. However, I also need to load Font Awesome (a dependency of Trumbowyg) from CDN, it does not work to include it via

@NpmPackage(value = "@fortawesome/fontawesome-free", version = "5.12.1")
@CssImport("@fortawesome/fontawesome-free/css/all.min.css")

or even via including its JavaScript by way of @JsModule.

However, even if my application at first seems to run via the CDN method, it stops working at some point and Vaadin crashes. Apparently the JavaScript from my app using Trumbowyg is having an effect on Vaadin - all I get after a while is the blue progress bar, but the page remains blank. No errors in the server log, nothing in the JavaScript console.

Is there any way to debug Vaadin itself if it cannot run its own JavaScript anymore?

I did at one point get a popup message saying that a script …/target/frontend/generated-flow-imports.js is not found.

Well, the latter problem is solved. I was running clean on my project while the application was running and that appears to remove some scripts that are needed in NPM mode. This was not a problem in Bower mode, where I could run “clean” whenever I wanted to get rid of old test reports.

The original question is still unsolved, though. There appears to be no reasonable way to include arbitrary JavaScript libraries that depend on each other. In Bower mode the @JavaScript and @Stylesheet declarations were just added to the HTML page, but in NPM mode those are compiled at build-time and then not available to the browser in the same way.

Apparently there is ABSOLUTELY NO WAY to include libraries from node_modules that depend on each other. Vaadin is hell-bent on isolating them from each other and the only way to stop Vaadin from doing that is by using a CDN.

The Page::addJavaScript command cannot be used, because it cannot read from node_modules. However, with webjars being taken away, this is where my dependency management lives and hence I have to take my libraries from there.

The @JavaScript annotation does NOT work with local files anymore as it did in Bower mode. If you give it a local file, it behaves like the @JsModule annotation, which loads the code isolated from every other piece of JavaScript.

The solution would, of course, be to make webpack bundle all dependencies together in one file. Which is what clientside development has looked like for years, but unfortunately Vaadin doesn’t support that. This is the first time in my personal Vaadin history, where Vaadin makes development harder, not easier.

Ulrich Mayring:
Apparently there is ABSOLUTELY NO WAY to include libraries from node_modules that depend on each other. Vaadin is hell-bent on isolating them from each other and the only way to stop Vaadin from doing that is by using a CDN.

I agree that it’s really hard to use some npm packages, for my example I used ion.rangeSlider.js and it was fine (Not easy, not well documented but possible).

I’ve updated the project and add an example with trumbowyg.
https://github.com/jcgueriaud1/vaadin-tooltip-js/commit/e23d9eea28d17667b8f766f33e9286f6fc0f2eff
The idea is:

  • Load JQuery as a global variable. It can be done with the jquery-loader.js file or by modifying the webpack (see jquery-loader.js in the project)
  • So the trumbowyg can be loaded (because Jquery is loaded)
  • import the Css file via CssImport annotations

You also need the svg: (here the commit for svg https://github.com/jcgueriaud1/vaadin-tooltip-js/commit/2f549172448ba297da80d46c78f43aacf164398c )
I found this solution on the trumbowyg website: https://alex-d.github.io/Trumbowyg/documentation/#svg-icons

  • So load the svg with raw loader in js, to allow that you need to configure the webpack.config.js (See the commit)
  • Add it to the document via js

The @JavaScript annotation does NOT work with local files anymore as it did in Bower mode. If you give it a local file, it behaves like the @JsModule annotation, which loads the code isolated from every other piece of JavaScript.

The main difference between Vaadin 13 and 14 is that Vaadin 14 loads your JavaScript (Javascript or JsModule) as a JS Module, which means that it runs in strict mode. With strict mode, it’s an error to reference a variable that isn’t declared. Without strict mode, it’s treated as a global variable.

This is why probably it’s quite hard with Jquery as many plugins are not working in strict mode. With external file it doesn’t matters.

Sorry about this experience, I hope you will be able to use Trumbowyg with webpack.

Ok, so the trick you use is to not import Trumbowyg, but a Trumbowyg loader, which then imports Trumbowyg via JS. What I did was import Trumbowyg directly, which then immediately looked for jQuery upon importing. I also came up with the mechanism to load jQuery via the webpack config, but where I gave up was the Trumbowyg plugins. Yes, Trumbowyg also has plugins and those need to see Trumbowyg and Trumbowyg needs to see its plugins upon initialisation. Plus, the Font Awesome icons couldn’t be seen by either Trumbowyg or its plugins. So I started to put everything in the webpack config, but then Vaadin killed the webpack config as well as package.json, when I cleaned the project. So apparently I am not meant to put anything in these files, since Vaadin keeps regenerating them (or is that a function of the Gradle plugin?).

What I can try is to put all this in the Trumbowyg loader, maybe that will work. But the whole process is super-involved and requires detailed technical knowledge of Vaadin and Webpack. Well, we all make mistakes and design errors once in a while, so hopefully Vaadin can rectify this conundrum. Sure, Polymer templates and web components are nice, but if you can’t make it super-easy to work with some basic jQuery stuff, then something’s wrong with the architecture.

Ok, I’m making progress, but not quite there yet!

Your idea to put the imports in an extra file was gold. Make a trumbowyg-loader.js like this:

import "trumbowyg/dist/trumbowyg.min.js";
import "trumbowyg/dist/plugins/colors/trumbowyg.colors.min.js";
... more Trumbowyg plugins ...

Then in the page do that:

@NpmPackage(value = "jquery", version = "3.4.1")
@NpmPackage(value = "trumbowyg", version = "2.21.0")
@JsModule("./src/jquery-loader.js")
@JsModule("./src/trumbowyg-loader.js")
@CssImport("trumbowyg/dist/ui/trumbowyg.min.css")
@CssImport("trumbowyg/dist/plugins/colors/ui/trumbowyg.colors.min.css")
... more Trumbowyg plugin CSS ...

At that point everything is loaded correctly and can be accessed from within the page via UI.getCurrent().getPage().executeJs("...");
The only problem remaining is Font Awesome. Just including it via @CssImport doesn’t work, as it’s not present when Trumbowyg initialises. I have tried to import "@fortawesome/fontawesome-free/js/all.min.js"; from within trumbowyg-loader.js, but that didn’t load the CSS.

With the @StyleSheet tag referencing Font Awesome on CDN everything works, so I suppose we need to find a similar trick to externalise the loading of the CSS.

That’s good news.

Do you have an error with font awesome?
To load the css of fontawesome you can use
@CssImport(“@fortawesome/fontawesome-free/css/all.min.css”), but that won’t load the font and you will have a 404 error (because no fonts).

Yes, you are right, I am getting 404s for loading the font. Why are you not getting them in your examples?

Font Awesome docs say:

Copy the entire /webfonts folder and the /css/all.css into your project’s static assets directory

There is probably some other way to do that (there is font-awesome loader) but I don’t think it’s really easy to do that.

You can do this to import font-awesome:

@JsModule(“@fortawesome/fontawesome-free/js/fontawesome”)
@JsModule(“@fortawesome/fontawesome-free/js/solid”)
@JsModule(“@fortawesome/fontawesome-free/js/brands”)

With css and cssimport, you need to copy the fonts. (probably not a bad solution :slight_smile: )

Hm, that doesn’t work for me. I am not getting any 404s anymore, which is probably to be expected, as the font files are now packed by Webpack. But they are packed in a way that my CSS rules don’t see them. I have code like this:

<button type="button" class="trumbowyg-bold-button " title="Bold (Ctrl + B)"></button>

.trumbowyg-bold-button::after {
    font-family: "Font Awesome 5\ Free";
    content: "\f032";
}

You don’t have the font Font awesome. you can show an icon using the css class like “fab fa-font-awesome”). Don’t know if you can in your case ( with trumbowyg). Probably easier to add and use the font as you did :slight_smile:

I’m not sure if there’s a difference between the two methods. The “fa” or “fab” class needs to have the reference to the font family as well. Unless you are talking about the SVG icons, which I believe are always fully embedded.

Anyway, the only thing that I have gotten to work with Font Awesome is using a CDN. I haven’t yet found a way to tell Vaadin / Webpack to copy the font files.

It would be interesting to know why you are attempting to integrate Trumbowyg library= There is Rich Text Editor component in our Pro collection https://vaadin.com/components/vaadin-rich-text-editor. It is based on Quill library, and one of the reasons why that was chosen, was that it does not depend on jQuery and thus behaves better in with rest of the Vaadin framework.