转载

JavaScript 太糟糕,JVM 有妙招!

JavaScript 太糟糕,JVM 有妙招!

虽然 JavaScript 凭借其简洁性、交互性等优势横扫了各大编程语言榜单,但是一直以来, JavaScript  应用程序的工具链极其复杂,引发不少开发者吐槽,在此,我们是否有更好的解决方案将其替代?

接下来,本文中将分享几个 JVM 的替代方案,希望对大家有所裨益。

JavaScript 太糟糕,JVM 有妙招!

作者 | Renato Athaydes

译者 | 弯月

责编 | 屠敏

出品 | CSDN(ID:CSDNnews)

以下为译文:

我是一个主要从事后端的开发人员,但时不时地也需要做一些前端工作。因为有时在个人的项目中也会用到前端。

出于这个原因,我一直在远远地关注JavaScript的发展,但说实话我并不喜欢JavaScript的世界。

JavaScript 太糟糕,JVM 有妙招!

我最不喜欢的一点是专业JavaScript应用程序的工具链极其复杂。从前,你只需要写几行HTML,然后用浏览器打开(file:///home/me/index.htlm)即可,在添加动态效果的时候只需要链接一个简单的JS文件,然后刷新一下页面就好了,但是这样的日子已经一去不复返了。

JavaScript的MDN页面(https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript)上展示的方法才是正确地使用HTML和JS的方法——不需要构建工具,也不需要框架。然而,很少有人听得进去。

如今,JavaScript的工具链包含了后端工具链所有的复杂性,有过之而无不及。甚至就在几年前,工具链的许多部分和现在都是不同的!看看StackOverflow上一些问题的答案(https://stackoverflow.com/questions/1480186/what-is-in-your-javascript-toolchain),你会有觉得很有意思。

这个Pluralsight JS开发环境(https://github.com/coryhouse/javascript-development-environment)建议你利用32种工具来构建你的应用程序(例如babel、chai、cheerio、eslint、mocha、webpack ......)。

2016年的这篇文章(https://italonascimento.github.io/configuring-a-basic-environment-for-javascript-development/)中记录了在Google和DuckDuckGo中搜索基本的JS开发环境时,搜索结果中包含哪些工具:

  • 依赖管理工具(npm或yarn)

  • 模块打包(webpack或gulp、browserify、bower)

  • ES6编译(babel)

  • 任务自动化(npm脚本)

  • 实时重新加载(live-server)

如果你按照上述文章的说明操作,那么最终会得到3个配置文件(npm、babel和webpack每个工具各一个),你需要通过嵌入到JSON配置文件的shell脚本来管理构建的过程,而且你的项目需要通过编译和实时重新加载HTTP服务器才能运行。

我认为如果我需要编译东西,那么至少我应该使用编译器来检查我的代码!

那么为什么不试试TypeScript呢?!

于是,我按照这个教程(https://alligator.io/typescript/new-project/)所说,创建了一个TypeScript项目。

创建好后,除了前面提到的那些配置文件之外,我还多了两个配置文件:tsconfig.json和tslint.json。

这一切太复杂了,所以Google创建了一个工具(Google TypeScript Style,简称GTS)来管理TypeScript配置。

JavaScript 太糟糕,JVM 有妙招!

我曾尝试使用这个工具,然而,由于一些模块相关的错误,浏览器拒绝运行我的JavaScript文件。我不知道在我使用的无数的工具中,哪一个可以解决这个问题,我快被这种错综复杂的工具逼疯了,于是我决定寻找其他的解决方案!

JavaScript 太糟糕,JVM 有妙招!

在寻找的过程中,我发现了这篇优秀的博客文章(https://itnext.io/you-might-not-need-a-build-toolchain-324edcef7f9a),文中宣称你不需要构建工具链。

这篇文章表明,你可以使用像React这样的现代工具,就不需要构建工具了!然而不幸的是,一旦你开始使用依赖项,而且你的应用程序包含几百行以上的JavaScript代码,那么这种方法就不太实用了。

我又回到了原点。

似乎无论我们做什么,构建/编译步骤都是一个避无可避的恶魔,而且长期以来在后端的开发工作中,我们了解了如何使用优秀的工具编写大型应用程序,所以何妨在前端的开发中也尝试使用这些工具呢?

毕竟,早在2011年,当我还在做Web开发时,就有人尝试这种做法了……例如,2011年我曾使用过GWT(这是一个基于Java的Web工具包)……当然,8年后,基于Java的Web应用程序应该有了很多改进,不是吗?

于是,我开始检查都有哪些工具可以用。一个习惯了使用Java世界里优秀的工具和良好生态系统的后端开发人员能否进军前端的工作,而不至于被JS世界里的疯狂湮没呢?

就让我们拭目以待吧!

JavaScript 太糟糕,JVM 有妙招!

方法

为了在本文中比较每一种备选的方案,我决定仿照上述我提到的博客文章“不需要构建工具链”,创建一个非常简单的计数器应用程序。

作为参考,以下是用React(没有JSX)编写的应用程序的代码。

我比较不同备选方案的方法非常简单:

  1. 找一个入门教程,然后通过运行演示的应用程序,感受设置的难易程度。

  2. 检查框架与Java开发人员期望的契合程度。

  3. 使用产品/框架提供的最基本的工具创建计数器应用程序。

  4. 衡量应用程序的大小、代码行数和性能。

最后一个要点的灵感来自这篇博客文章“前端框架的真实比较”(https://medium.freecodecamp.org/a-realworld-comparison-of-front-end-frameworks-with-benchmarks-2019-update-4be0d3c78075)。

应用程序大小是通过查看浏览器的网络选项卡来确定的(为了避免使用依赖于大量非JS资源的框架,我包含了所有类型的资源)。

对于性能,我使用了Google Chrome自带的Lighthouse。

以下是使用React的结果,仅供参考。

为了进行比较,我选择从本地服务器(而不是CDN)上下载并提供React JS文件。

应用程序的大小:

JavaScript 太糟糕,JVM 有妙招!

性能:

JavaScript 太糟糕,JVM 有妙招!

好了,以上就是我们的对比标准。

现在,让我们来看看最原始的基于Java的Web工具包:GWT。

JavaScript 太糟糕,JVM 有妙招!

GWT:Java-source-to-JS,服务器和客户端框架

网址:http://www.gwtproject.org

GWT是第一个在没有Applet或插件的情况下,在浏览器中使用Java的工具,创建于2006年。这是一项非常成熟的技术,但自2013年以来Google支持的框架不再支持GWT……我记得当时Google将其Web开发工作的重点放在了Dart上,而Dart作为JS的替代方案与GWT有着直接的竞争关系,而且当时的GWT社区已被人遗弃。不知怎地,GWT这个项目依然存在,所以我觉得我必须把它包含在这个比较中,这样才能真正看出现代替代方案的状况。

如果你想使用GWT,那么可以按照官网的建议下载SDK或安装Eclipse插件……我已经有很多年没碰过Eclipse了(我改用了IntelliJ),所以我决定下载SDK:

./webAppCreator -out gwt-app com.athaydes.GwtApp

这个命令运行得非常快,而且还创建了一个Ant项目(什么东西?!)。我使用Java的时间已经超过了10年,而且我错过了Ant的鼎盛时期——如果我没记错的话,Maven是Java的构建工具。

但我不介意Ant!稍后我可以将其转换为Gradle或Maven构建。而且IntelliJ可以很好地支持Ant项目,所以你可以在IntelliJ中打开这个项目,而且一切都会正常工作。

运行演示应用程序:

ant devmode

这一步可以打开一个Swing应用程序,你可以通过它控制开发服务器!我感觉它与2009年一模一样!在浏览器上打开这个应用后,我发现除了一些新鲜的样式之外,这个应用也几乎没有变化。

演示应用会向你展示如何使用GWT最强大的功能:无缝的RPC框架让开发人员几乎忘记了客户端和服务器之间的区别,两者之间的通信犹如调用Java方法一样简单。

但是在构建计数器应用的时候,我们并不需要两者之间的通信。所以,我做的第一件事就是从入门应用中删除RPC的代码。

一般来说,GWT应用程序只包含一个源代码根,它分为:

  • 客户端:仅在浏览器中运行的客户端代码。

  • 服务器:服务器端代码,即后端。

  • 共享:客户端和服务器皆可见的代码。

你可以通过应用最顶层软件包的GwtApp.gwt.xml文件,设置应用客户端可以看到哪些软件包。该文件还可以控制应用应该使用的GWT主题,因此你可以很轻松地在应用中使用黑暗主题。

在这个入门的应用程序中,Java源代码位于src/目录下,而Web的源代码位于war/目录中。初始页面的HTML文件是war/GwtApp.index.html。打开这个文件,你可以看到一个非常普通的HTML,但奇怪的是,大部分视图都已经实现了(利用<table>布局实现的,显然,这是2008年前后的程序)。

我想用自己的代码实现视图,所以我删除了这个<table>,并添加了<div id="content"></div>。

在基于小部件的API的帮助下,在GWT中实现用户界面是一件非常容易的事情。因此,我在5分钟内就创建计数器应用(请不要忘记我之前有过使用GWT的经验,即便我几乎什么都想不起来了)。但是,当我尝试重新编译代码时,遇到了lambdas的错误——ant文件说我们需要使用Java 7!为什么没人更新呢?!GWT支持Java 8,所以我将其更新到了Java 8,然后一切都正常了。

以下是我在GWT中实现计数器应用的代码:

package com.athaydes.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.*;

import java.util.function.Consumer;

public class GwtApp implements EntryPoint {
    public void onModuleLoad() {
        RootPanel.get( "content" ).add( new Counter() );
    }
}

class Counter extends Composite {

    private int value;

    Counter() {
        HorizontalPanel buttonsPanel = new HorizontalPanel();
        buttonsPanel.setSpacing( 10 );

        Button up = new Button( "Increment" );
        Button down = new Button( "Decrement" );
        Label out = new Label();

        Runnable update = () -> out.setText( "The current count is " + value );
        update.run();

        Consumer<Boolean> handler = ( increment ) -> {
            if ( increment ) {
                value++;
            } else {
                value--;
            }
            update.run();
        };

        up.addClickHandler( clickEvent -> handler.accept( true ) );
        down.addClickHandler( clickEvent -> handler.accept( false ) );

        buttonsPanel.add( up );
        buttonsPanel.add( down );

        VerticalPanel root = new VerticalPanel();
        root.setSpacing( 20 );

        root.add( buttonsPanel );
        root.add( out );

        initWidget( root );
    }

}

看起来不错。实时地重新加载适用于超级开发模式,可以快速地修复用户界面中的小问题。

编译出该应用的生产版本:

ant build

生成的JS文件放在war/gwtapp目录中。通过war目录就可以在浏览器中运行应用,所以我只需在IntelliJ中打开war/GwtApp.html,然后单击右上角的浏览器图标,就可以让IntelliJ启动一个负责运行HTML文件的HTTP服务器,并在我想要的浏览器中打开HTML文件。结果表明,该应用运行良好。

应用程序的大小:

JavaScript 太糟糕,JVM 有妙招!

性能:

JavaScript 太糟糕,JVM 有妙招!

JavaScript 太糟糕,JVM 有妙招!

TeaVM:Java-bytecode-to-JS编译器

网址:http://teavm.org/

TeaVM这个项目据称可以将Java字节码转换成JavaScript和Webassembly。 因为它不需要Java源代码,所以它也可以用于从Kotlin和Scala(以及其他基于JVM的语言)进行编译。

如果你想学习使用TeaVM,那么需要使用Maven archetype:

$ mvn -DarchetypeCatalog=local /                
  -DarchetypeGroupId=org.teavm   -DarchetypeArtifactId=teavm-maven-webapp   -DarchetypeVersion=0.5.1 archetype:generate

这一步会创建一个标准的Java项目,根据Maven的约定,你可以在提示框中输入项目的保存目录。Java源代码位于src/main/java,而Web的index.html等文件位于src/main/webapp目录中。

以下是我编写的计数器的Java代码:

package com.athaydes;

import org.teavm.jso.dom.html.HTMLButtonElement;
import org.teavm.jso.dom.html.HTMLDocument;
import org.teavm.jso.dom.html.HTMLElement;

import java.util.function.Consumer;

public class Client {
    public static void main( String[] args ) {
        HTMLDocument document = HTMLDocument.current();
        HTMLElement div = document.createElement( "h2" );
        div.appendChild( document.createTextNode( "TeaVM CounterApp" ) );
        document.getBody().appendChild( div );
        div.appendChild( new Counter().build() );
    }
}

class Counter {
    private int value = 0;

    HTMLElement build() {
        HTMLDocument document = HTMLDocument.current();
        HTMLButtonElement up = ( HTMLButtonElement ) document.createElement( "button" );
        up.appendChild( document.createTextNode( "Increment" ) );
        HTMLButtonElement down = ( HTMLButtonElement ) document.createElement( "button" );
        down.appendChild( document.createTextNode( "Decrement" ) );
        HTMLElement out = document.createElement( "p" );

        Runnable update = () -> out.setInnerHTML( "The current count is " + value );

        Consumer<Boolean> handler = ( increment ) -> {
            if ( increment ) {
                value++;
            } else {
                value--;
            }
            update.run();
        };

        up.listenClick( ( event ) -> handler.accept( true ) );
        down.listenClick( ( event ) -> handler.accept( false ) );

        update.run();

        HTMLElement div = document.createElement( "div" );
        div.appendChild( out );
        div.appendChild( up );
        div.appendChild( down );

        return div;
    }
}

有趣的是,Java Counter组件仅包含34行代码,比React.js(55行以上的代码量)少很多!

运行如下命令编译:

$ mvn package

该命令会像往常一样编译所有的Java代码,并在target/classes目录中生成.class文件(与普通的Java项目一样),但它还会在target/<project-name>-<version>/teavm下创建JavaScript代码,我的项目名为mytea,因此相应的目录为:target/mytea-1-0-SNAPSHOT/teavm。

在运行该应用程序时,你只需启动Web服务器即可将目录`target/mytea-1-0-SNAPSHOT/挂出来。

应用程序的大小:

JavaScript 太糟糕,JVM 有妙招!

性能:

JavaScript 太糟糕,JVM 有妙招!

JavaScript 太糟糕,JVM 有妙招!

JSweet:带有库生态系统的Java-source-to-JS(和TypeScript)编译器

网址:http://www.jsweet.org/

JSweet可以将Java源代码编译为TypeScript和JavaScript。Java库可以作为TypeScript库发布。与其他备选方案不同,Java代码可以与JS / TS代码互相转换!

如果你想学习使用JSweet,那么我推荐你从GitHub克隆快速入门的项目:

$ git clone https://github.com/cincheo/jsweet-quickstart.git
$ cd jsweet-quickstart
$ mvn generate-sources

同样,Java项目会使用标准的Maven约定,将Java源代码放入src/main/java中。然而,Web资源会直接放在webapp目录下。

与TeaVM不同,JSweet编译器根本不会生成类文件:这就是为什么你只能运行mvn generate-sources来生成JS代码,而无法使用更常见的mvn package或mvn install。

最终我写的Java代码几乎与TeaVM一样(但有一点例外:JSweet API选择java.util.function.Function作为点击处理程序,而不是Consumer<Event>,因此必须返回一个值,用lambda来编写这段代码就会很尴尬,你需要使用大括号并强制返回null):

package quickstart;

import def.dom.HTMLButtonElement;
import def.dom.HTMLElement;

import java.util.function.Consumer;

import static def.dom.Globals.document;

public class QuickStart {
    public static void main( String[] args ) {
        HTMLElement div = document.createElement( "h2" );
        div.appendChild( document.createTextNode( "JSweet CounterApp" ) );
        document.body.appendChild( div );
        div.appendChild( new Counter().build() );
    }
}

class Counter {
    private int value = 0;

    HTMLElement build() {
        HTMLButtonElement up = ( HTMLButtonElement ) document.createElement( "button" );
        up.appendChild( document.createTextNode( "Increment" ) );
        HTMLButtonElement down = ( HTMLButtonElement ) document.createElement( "button" );
        down.appendChild( document.createTextNode( "Decrement" ) );
        HTMLElement out = document.createElement( "p" );

        Runnable update = () -> out.innerText = "The current count is " + value;

        Consumer<Boolean> handler = ( increment ) -> {
            if ( increment ) {
                value++;
            } else {
                value--;
            }
            update.run();
        };

        up.onclick = ( event ) -> {
            handler.accept( true );
            return null;
        };
        down.onclick = ( event ) -> {
            handler.accept( false );
            return null;
        };

        update.run();

        HTMLElement div = document.createElement( "div" );
        div.appendChild( out );
        div.appendChild( up );
        div.appendChild( down );

        return div;
    }
}

应用程序的大小:

JavaScript 太糟糕,JVM 有妙招!

性能:

JavaScript 太糟糕,JVM 有妙招!

JavaScript 太糟糕,JVM 有妙招!

CheerpJ:完全在浏览器上实现JVM

网址:https://leaningtech.com/cheerpj/

CheerpJ绝对是最疯狂的选择(甚至可能是你见过的最疯狂的项目!)

实际上,它并不是Java到JavaScript的转换器……其实它是一个完整的Java运行时!没错,它可以完成更传统的JVM可以执行的所有操作。线程、文件系统(显然它通过IndexDB模拟文件系统)、反射、类加载、网络,等等。

如果这还不足以让你头晕目眩,那么就让我们来看看CheerpJ的入门教程。

这个入门教程可以让你感受如何在没有插件的情况下,在浏览器上运行包含Swing应用(记住Swing,面向桌面系统的多平台Java UI框架??)的编译好的jar。

它运行的不是applet,它真的是在浏览器中运行Swing代码!!而且还没有插件!!

说出来连我自己都不相信,所以让我们一起来看看这个入门教程吧。

为了方便起见,我将这些步骤进行了如下总结:

  • 下载Swing应用程序的jar。

  • 使用本地Java运行这个jar。

  • 安装CheerpJ(据说,你必须手动下载并放在你的主文件夹下)。

  • 确认你安装好了python3(对啊,我也觉得很奇怪,使用CheerpJ的时候,还需要用到Python)。

  • 运行~/cheerpj_1.3/cheerpjfy.py TextDemo.jar。

  • 创建一个index.html文件。

  • 让Web服务器指向当前目录。

目前看起来这个入门教程行不通啊,因为你一直卡在了加载中……但是你需要漫长的等待,长到你开始怀疑人生!但最后终于成功了!

这个Swing应用跟我在本地Java上运行的应用相同,但它嵌入到了浏览器的网页中!

JavaScript 太糟糕,JVM 有妙招!

在Ubuntu上运行Swing TextDemo

JavaScript 太糟糕,JVM 有妙招!

在浏览器中通过CheerpJ运行Swing TextDemo

如果你有现成的Swing应用,那么可能CheerpJ适合你。但是如果你想要写一个新的应用,那么你可能不想选用CheerpJ。

我尝试使用CheerpJ对DOM的支持(发行包中包含cheerpj-dom.jar文件)来实现计数器应用,但是我在10分钟内遇到了2个bug。而且这两个bug很麻烦,我完全无法继续……更别提我必须将每个Java String都包装成对Global.JSString()的调用!

给CheerpJ开发人员的一点提示:将你的jar放入Maven库中(Java开发人员都不愿意将依赖项硬编码到本地文件),千万不要放在目录下,绝对行不通!另外,你需要习惯语言的约定:不要调用Java方法set_onclick,你应该调用setOnClick,谢谢。

我准备放弃CheerpJ了,但后来我想起了此次实验的一项承诺:

使用产品/框架提供的最基本的工具创建计数器应用程序。

无奈,只能硬着头皮继续了。对CheerpJ来说,似乎实现用户界面的基本工具即是JVM本身!这个JVM提供了一个UI工具包:Swing!

从Java 12开始,Swing是随JVM一起发布的唯一UI工具包……JavaFX已经成为一个独立的项目。不管怎样,CheerpJ只支持Swing。

所以,如下是我在纯Swing中实现的计数器应用:

import javax.swing.*;
import java.awt.*;
import java.util.function.Consumer;

public class Main implements Runnable {

    @Override
    public void run() {
        JFrame frame = new JFrame( "CheerpJ Demo" );

        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setSize( 400, 80 );
        frame.add( new Counter() );
        frame.setVisible( true );
    }

    public static void main( String[] args ) {
        SwingUtilities.invokeLater( new Main() );
    }
}

class Counter extends JPanel {
    private int value;

    public Counter() {
        JButton up = new JButton( "Increment" );
        JButton down = new JButton( "Decrement" );
        JLabel out = new JLabel();

        Runnable update = () -> out.setText( "The current count is " + value );
        update.run();

        Consumer<Boolean> handler = ( increment ) -> {
            if ( increment ) {
                value++;
            } else {
                value--;
            }
            update.run();
        };

        up.addActionListener( event -> handler.accept( true ) );
        down.addActionListener( event -> handler.accept( false ) );

        JPanel buttonsPanel = new JPanel();
        buttonsPanel.add( up );
        buttonsPanel.add( down );

        JLabel title = new JLabel( "CheerpJ CounterApp (Swing)" );

        setLayout( new BorderLayout() );
        add( title, BorderLayout.PAGE_START );
        add( buttonsPanel, BorderLayout.PAGE_END );
        add( out, BorderLayout.CENTER );
    }
}

你完全可以在桌面系统上正常运行:这完全就是一个普通的Swing应用!

首先,我们需要创建普通的jar:

mvn package

然后用Java运行:

java -jar cheerpj-swing/target/cheerpj-swing-1.0-SNAPSHOT.jar

结果如下:

JavaScript 太糟糕,JVM 有妙招!

在Ubuntu中运行计数器应用

疯狂的是你也可以在CheerpJ的浏览器中运行该应用。

看一看浏览器上的“网络”选项卡,你会发现非常有趣的东西:它一直在下载东西,显然每个Java的标准库包都有一个JS文件,CheerpJ会根据需要下载它们。

应用程序的大小:

JavaScript 太糟糕,JVM 有妙招!

性能:

JavaScript 太糟糕,JVM 有妙招!

为了方便你了解该应用有多糟糕,请参照Lighthouse 提供的如下诊断信息:

JavaScript 太糟糕,JVM 有妙招!

这是我第一次看到Avoid enourmous network payloads的诊断,我绝对会遵循这个建议!

JavaScript 太糟糕,JVM 有妙招!

Vaadin Flow:Java-source-to-JS-source,服务器和客户端框架

网址:https://vaadin.com/flow

很久以前我就听说Vaadin是一个基于GWT的框架……但似乎他们最近创建了一个基于Web组件的产品Vaadin Flow。

我喜欢网络组件,所以我决定试试看。

入门教程的页面里有几个选项,可以创建与Spring或CDI等集成的项目。我选择了我能找到的最基本选项Project Base,因为我想要的只是一个客户端应用程序。

我按照说明下载了zip,而且只用了几分钟就可以让这些代码运行了。这是一个熟悉的Maven项目(我希望Gradle成为最受欢迎的选择,但显然情况并非如此)......我没有看到index.html文件,这有点令人惊讶。浏览器是如何加载应用程序的呢?

不管怎样,我们只需要一个命令就可以运行这个应用:mvn jetty:run,然后访问http://localhost:8080就能看到正在运行的应用。

看一下这个巨大的POM文件(一个hello world应用就有139行的代码),似乎他们使用了jetty-maven-plugin。

所以,我再一次选用了我最喜欢的选项IntelliJ服务器,来提供index.html文件。然而,情况开始有点混乱,这对我来说有点太神奇了......当然,我是一名Java开发人员,但也没关系,我还是可以应付一两个HTML文件。

在我改代码的时候,似乎服务器重启了,所以我感觉它可能在实时重新加载。这很好!但不幸的是,虽然不知道服务器在干什么,但是它并没有实时地重新加载我的代码。我试过几次刷新页面,却导致了几处报错!!看起来像Vaadin应用不喜欢页面刷新(应用处于调试模式,不过我估计它背后的工作太多了,结果因自身的复杂性而崩溃)。

但是,编写UI的Java API非常出色,非常易于使用和学习,显然是为Java程序员编写的,让我感觉宾至如归,简直太好了!

如下是我编写的计数器应用,你有没有觉得这段代码很酷:

package com.example.test;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;

import java.util.function.Consumer;

@Route( "" )
public class MainView extends VerticalLayout {

    public MainView() {
        H2 header = new H2( "Vaadin Flow CounterApp" );
        add( header, new Counter() );
    }
}

class Counter extends Composite<Div> {
    private int value;

    Counter() {
        Paragraph out = new Paragraph();
        Runnable update = () -> out.setText( "The current count is " + value );
        update.run();

        Consumer<Boolean> handler = ( increment ) -> {
            if ( increment ) {
                value++;
            } else {
                value--;
            }
            update.run();
        };

        Button up = new Button( "Increment", event -> handler.accept( true ) );
        Button down = new Button( "Decrement", event -> handler.accept( false ) );

        getContent().add( out, up, down );
    }
}

为了测量这个应用的性能,我通过如下命令在生产模式下运行了该应用:

$ mvn jetty:run-exploded -Pproduction-mode

感觉这个页面非常生气勃勃,即便没有经过特殊的设计,页面风格也非常好。

应用程序的大小:

JavaScript 太糟糕,JVM 有妙招!

注意:这个Vaadin的计数器应用至少需要312KB才能运行(主要是因为一个巨大的HTML文件,vaadin-flow-bundle:JS资源的总大小只有46.7KB)……这比我想象的还要多。

性能:

JavaScript 太糟糕,JVM 有妙招!

在运行应用时,Chrome在控制台上显示了一个警告,这有点让人担心。

似乎Vaadin在很早就采用了HTML导入等实验性的功能,而现在这些功能都被弃用了。很可惜,但是如果他们能够持续更新这个产品的话,这应该不是一个大问题。

Vaadin比JSweet和TeaVM慢一点,但公平地说,这个版本的计数器比其他版本更强大,尽管这并非我所愿(有没有办法从Vaadin应用中删除样式和字体?)!

JavaScript 太糟糕,JVM 有妙招!

Bck2Brwsr:Java-bytecode-to-JS编译器

网址:http://wiki.apidesign.org/wiki/Bck2Brwsr

Bck2Brwsr始于2012,旨在创建一个能够快速启动并百分百在现代浏览器中运行的小型Java。

然而,与CheerpJ不同,它未能实现完整的JVM,只有一小部分功能可以在浏览器上运行,更类似于TeaVM,因为它也是将Java字节码编译成JS。

apidesign.org上有一个看起来很旧的wiki页面(http://wiki.apidesign.org/wiki/Bck2Brwsr),说明了如何使用bck2brwsr。页面上建议的一种做法是使用Bck2BrwsrViaCLI,还有一种是Knockout4Java。

我并不是很想使用Knockout.js(它自2015年左右开始就在JS的世界中失宠了),所以我选择了CLI。

我按照wiki所说,运行Maven原型创建了一个新项目:

$ mvn archetype:generate     -DarchetypeGroupId=com.dukescript.archetype     -DarchetypeArtifactId=knockout4j-archetype  -DarchetypeVersion=0.26     -Dwebpath=client-web

小心wiki页面上给出的命令!最后一行少了一个反斜杠,而且命令中的版本是0.16,当前版本应该是0.26。

然后,编译并打包应用:

$ mvn package 

警告:这一步会打开一个带有JUnit Browser Runner的弹出窗口。

我没想到会弹出这个窗口……不过,没关系,我们继续,wiki页面上说我们可以通过以下命令运行带有FXBrwsr的应用:

$ mvn -f client process-classes exec:exec

但是,我在运行这个命令的时候,收到了如下的错误:

Exception in thread "main" java.lang.IllegalStateException: Can't find any Fn.Presenter
    at net.java.html.boot.BrowserBuilder.showAndWait(BrowserBuilder.java:255)
    at com.athaydes.Main.main(Main.java:16)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

我快速搜索了Google,结果什么都没找到(我猜测他们没有在Linux上测试),所以我尝试了一下在真正的浏览器中运行应用:

$ mvn -f client-web clean package -DskipTests bck2brwsr:show

结果,又遇到了另外一个错误:

[ERROR] Failed to execute goal on project bck2brwsr-web: Could not resolve dependencies for project 
com.athaydes:bck2brwsr-web:jar:1.0-SNAPSHOT: 
Could not find artifact com.athaydes:bck2brwsr-js:jar:bck2brwsr:1.0-SNAPSHOT -> [Help 1]

这是一个Maven经典的问题:有时你需要把你的应用放到某些插件可见的地方……通常你可以通过下述命令解决这个问题:

$ mvn install

然后,再次运行命令启动应用,看似没问题,但应用毫无反应。

检查控制台,果然出错误了:

VM69 bck2brwsr.js:6 Uncaught Cannot find com.athaydes.BrowserMain
w @ VM69 bck2brwsr.js:6
v @ VM69 bck2brwsr.js:2
r @ VM69 bck2brwsr.js:2
load (async)
z @ VM69 bck2brwsr.js:3
r @ VM69 bck2brwsr.js:4
w @ VM69 bck2brwsr.js:6
n.loadClass @ VM69 bck2brwsr.js:6
(anonymous) @ index.html:28

我翻看了代码……希望找到一些可以帮助我了解这个问题的东西,结果却什么都没找到……

然而,我找到了大量的XML。准确地说,在4个Maven pom文件中找到了931行XML。

我还发现该项目包含3个Maven模块:client、client-web和js。

js模块包含一个Java类,其中包含一些标记为native的方法,还用@JavaScriptBody进行了注释,其中包含实现方法的JavaScript代码,但是嵌入到了Java Strings中。

client模块包含一个bootstrapping主类以及一个带有更多注释的DataModel类,似乎是为了将数据模型绑定到视图,同时尝试使用js模块中声明的函数。

它引用了另一个名为Data的类,然而源代码中并没有这个类……它是在编译项目时自动生成的。而我却搞不清楚这个类是由什么生成的。可能深埋在1000行的pom中吧。

client-web模块包含缺少的类:com.athaydes.BrowserMain。因为这是我们在上述Maven命令中运行的模块,所以我不明白为什么浏览器找不到这个类。

我希望能够使用DOM API来创建UI,就像之前的一些示例一样。我想了很多办法,最终只发现了一些NetBeans API,这绝对不是我想要的东西。

似乎唯一可以与网页交互的方法就是通过HTML文本上的data-bind属性,其背后的支持是Knockout.js!

我以为我已经避开了Knockout,但是看看“CLI”页面上显示的Maven原型的ID吧!

也许我可以通过可怕的@JavaScriptBody注释,直接在Java Strings中编写JavaScript代码(请别这么干……理智一点),但我并不想知道这样是否可行。

我觉得做了这么多尝试就够了。因为几乎没有文档可以帮助我调试这个问题,在线搜索答案也是浪费时间——根本就没有答案。

我很抱歉无法在这篇文章中给出有关bck2brwsr性能的实际数据,虽然我花了一天的时间,却未能成功。我觉得我可以说除非你有迫不得已的理由,否则就不应该碰任何与这个项目相关的东西,包括DukeScript。

你可能会注意到,整个bck2brwsr文档甚至源代码中多次引用了DukeScript。DukeScript的入门页面中显示的示例与我使用的Maven原型创建的示例完全相同,如此看来这两个项目似乎密切相关,尽管Dukescript创始人说bck2brwsr只是项目的一小部分,而且也属于实验性质,另外他们也支持TeaVM。

JavaScript 太糟糕,JVM 有妙招!

结论

让我们看看最后的结果:

我没有将Bck2brwsr包含在内,因为我未能调试成功。如果有人能告诉我问题所在,那么我会更新下面的结果。

性能:

JavaScript 太糟糕,JVM 有妙招!

* FCP = First Contentful Paint(此值用于打破性能相同的工具之间的联系)

应用程序的大小:

JavaScript 太糟糕,JVM 有妙招!

代码行数:

JavaScript 太糟糕,JVM 有妙招!

代码行数不包括导入和注释。

JavaScript 太糟糕,JVM 有妙招!

总结

问题:性能至关重要?

答案:那么你应该选择JSweet或TeaVM。

问题:你喜欢占用空间较小的方案?

答案:那么JSweet可以轻松胜任,但TeaVM也相当不错。

问题:你希望维护的代码库最小?

答案:那么Vaadin Flow非常棒。其他方案都非常接近,所以这个因素不会产生太大的影响......但它们都能够轻松击败React!

问题:你想要一些尽可能接近普通Java的东西?

答案:如果你关心UI组件的话,可以选择Vaadin Flow;如果你关心业务逻辑的话,则可以选择TeaVM。

问题:你希望尽可能接近JS生态系统,但仍使用Java工具?

答案:那么JSweet非常适合你。

问题:你想要的东西是Java,但希望在浏览器中运行?

答案:那么JSweet非常适合你。

问题:你想尝试一些与Java不同的东西,而不是JS?

答案:Elm是一种现成的函数式编程语言,旨在创建Web应用程序。它有很棒的工具,包括一个很棒的IntelliJ插件,所以非常适合那些一直想要功能的Java开发人员!另外一个理想的选择是Dart,它与Java非常相似,但有很多语法糖,所以常见的功能都很容易实现。Ant带有很棒的工具,包括实时重新加载的服务器,还包括类似于Angular和React等的框架(如果你需要的话)。

如果你对JVM备选方案与Dart的比较感到好奇,那么我可以告诉你我已经将Dart的实现添加到了我的GitHub代码库中。比较结果如下:

应用程序大小:79KB,性能:100(FCP:1.4s),代码行数:31。

本文中显示的所有代码都上传到了如下GitHub代码库中,包括React.js和Dart实现。

https://github.com/renatoathaydes/jvm-alternatives-to-js

原文:https://renato.athaydes.com/posts/comparing-jvm-alternatives-to-js.html

本文为CSDN翻译,转载请注明来源出处。

【END】

JavaScript 太糟糕,JVM 有妙招!

作为码一代,想教码二代却无从下手:

听说少儿编程很火,可它有哪些好处呢?

孩子多大开始学习比较好呢?又该如何学习呢?

最新的编程教育政策又有哪些呢?

下面给大家介绍CSDN新成员: 极客宝宝(ID: geek_baby)

戳他了解更多↓↓↓

JavaScript 太糟糕,JVM 有妙招!

 热 文推 荐 

  李彦宏候选工程院院士;陌陌回应探探下架;拼多多回应“刷单”质疑 | 极客头条

  华为硬核招聘程序员!| 极客头条

他 25 岁进贝尔实验室,32 岁创建信息论,40 岁办达特茅斯会议 | 人物志

☞真の硬核粉丝!小学生也参加杨超越杯,作品优秀!

☞不改变比特币, 如何扩容?

☞强推!盘点阿里巴巴 15 款开发者工具 | 程序员硬核评测

☞17篇论文入选CVPR 2019,百度AI都在关注什么?(附论文地址)

☞ 她说:为啥程序员都特想要机械键盘?这答案我服!

System.out.println("点个在看吧!");
console.log("点个在看吧!");
print("点个在看吧!");
printf("点个在看吧!/n");
cout << "点个在看吧!" << endl;
Console.WriteLine("点个在看吧!");
Response.Write("点个在看吧!");
alert("点个在看吧!")
echo "点个在看吧!"

点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

你点的每个“在看”,我都认真当成了喜欢

原文  http://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650719397&idx=3&sn=17e4f792fe9caaccb544e92a853fefe8
正文到此结束
Loading...