Running a Javascript-based CSS reducing utility from GraalVM Java/Spring

Viewed 205

I selected GraalVM as it looked like a great way to execute npm modules from Java code. In my use-case I'm wanting to use something like PurgeCSS to reduce CSS file sizes in my Spring application. A controller would call PurgeCSS that would in turn cache a CSS file and then later serve the cached version.

What design pattern can I use to execute a JavaScript-based CSS reducing/purging utility from Java code?

It seems that there are two flavours of JavaScript that GraalVM Polyglot / Truffle support. Node flavour (accessible from the command-line only), and ECMAScript flavour (accessible programatically only). And it seems that the two cannot interact with one another. Please correct me.

This is what I tried: Using GraalVM's npm install purgecss, which worked great, then the following code which didn't work.

@Component
public class JavaScript {
    String js = "var Purgecss = require('purgecss')\n" +
            "const purgeCss = new Purgecss({\n" +
            "  content: ['**/*.html'],\n" +
            "  css: ['**/*.css']\n" +
            "})\n" +
            "const purgecssResult = purgecss.purge()";

    public void testJavaScript() {
        try (Context context = Context.create()) {
            Value function = context.eval("js", this.js);
            assert function.canExecute();
            String response = function.execute().asString();
            System.out.println(response);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Seems that require is a node-specific thing so that doesn't work. Unfortunately the whole of purgeCSS is filled with require's so I assume that puts the whole library out.

  • Would I have to find a single file pure .mjs CSS library (ESM version)?
  • Or is there a way to specify node favour JavaScript, enabling require?
  • Or can I replace all the require's with load's?

Any help or direction on how to make GraalVM's JavaScript functionality work in this use-case is greatly appreciated.

PS: I'm running Spring via Gradle. Was previously looking at hooking Spring up to Gulp via the Gradle Wrapper and then executing CSS tasks via Gulp. I'm hosting on Heroku and see they do allow multiple buildpacks (Java and Node)

1 Answers

This is by no means a definitive answer but more a Work-In-Progress. It seems that GraalVM supports experimental features that allow parsing of node-style JavaScript eg. with require() or fs()

I found a link to an article:

Experimental support for CommonJS Npm modules in the Context API

https://github.com/graalvm/graaljs/blob/master/docs/user/NodeJSVSJavaScriptContext.md#experimental-support-for-commonjs-npm-modules-in-the-context-api

My implementation

    public void testJavaScript() {

        String node_modules = System.getProperty("user.dir") + "/node_modules";

        Context cx = Context.newBuilder("js")
                .allowAllAccess(true)
                .allowIO(true)
                .allowHostAccess(true)
                .option("js.commonjs-require", "true")
                .option("js.commonjs-require-cwd", node_modules)
                .build();
        cx.eval("js", "require('purgecss')");
    }

Note!

It seems to me that the allowExperimentalOptions() method on the Builder is no longer in the API and has been replaced by the much more dangerous sounding .allowAllAccess() method that enables require() to work.

Note 2

GraalVM has its own commandline version of the 'node' and 'npm' commands. On my machine it hijacked the default node and npm commands and replaced them with the GraalVM version. Typing which npm and which node (Linux/Mac) will make it clear which version you're using. I just used npm init in the root of my Java Project followed by npm i whatever-package and it seems to work.

Only issue I have now is a module trying to access what seems like a system module called 'os'. Ideas appreciated. Looks like browserify may just do the trick (especially that it seems to have a module called browserify-os).

Related