1. Overview
1.概述
In this tutorial, we’ll understand what exactly is an isomorphic app. We’ll also discuss Nashorn, the JavaScript engine bundled with Java.
在本教程中,我们将了解究竟什么是同构应用程序。我们还将讨论Nashorn,即与Java捆绑的JavaScript引擎。
Furthermore, we’ll explore how we can use Nashorn along with a front-end library like React to create an isomorphic app.
此外,我们将探讨如何将Nashorn与React等前端库一起使用,以创建一个同构的应用程序。
2. A Little Bit of History
2.一点点历史
Traditionally, client and server applications were written in a manner that was quite heavy on the server-side. Think of PHP as a scripting engine generating mostly static HTML and web browsers rendering them.
传统上,客户端和服务器端应用程序的编写方式是相当重于服务器端的。把PHP想象成一个脚本引擎,生成大部分静态的HTML,然后由Web浏览器来渲染它们。
Netscape came with the support of JavaScript in its browser way back in mid-nineties. That started to shift some of the processing from server-side to the client-side browser. For a long time, developers struggled with different issues concerning JavaScript support in web browsers.
早在九十年代中期,Netscape就在其浏览器中加入了对JavaScript的支持。这使得一些处理工作开始从服务器端转移到客户端的浏览器上。在很长一段时间里,开发者们都在为网络浏览器中的JavaScript支持问题而努力。
With the growing demand for faster and interactive user experience, the boundary was already being pushed harder. One of the earliest frameworks that changed the game was jQuery. It brought several user-friendly functions and much-enhanced support for AJAX.
随着对更快和互动的用户体验的需求不断增加,这一界限已经被进一步推开。最早改变游戏规则的框架之一是jQuery。它带来了几个用户友好的功能,并大大增强了对AJAX的支持。
Soon, many frameworks for front-end development started to appear, which improved the developer’s experience greatly. Starting with AngularJS from Google, React from Facebook, and later, Vue, they started to capture developer attention.
很快,许多用于前端开发的框架开始出现,这大大改善了开发人员的体验。从Google的AngularJS、Facebook的React以及后来的Vue开始,它们开始吸引开发者的注意力。
With modern browser support, remarkable frameworks and required tools, the tides are largely shifting towards client-side.
随着现代浏览器的支持、卓越的框架和所需的工具,浪潮在很大程度上转向了客户端。
An immersive experience on increasingly faster hand-held devices requires more client-side processing.
在越来越快的手持设备上的沉浸式体验需要更多的客户端处理。
3. What’s an Isomorphic App?
3.什么是同构的应用程序?
So, we saw how front-end frameworks are helping us develop a web application where the user interface is completely rendered at the client-side.
因此,我们看到了前端框架是如何帮助我们开发一个用户界面完全在客户端呈现的网络应用。
However, it’s also possible to use the same framework at the server-side and generate the same user interface.
然而,在服务器端也可以使用相同的框架并生成相同的用户界面。
Now, we do not have to stick to client-side only or server-side only solutions necessarily. A better way is to have a solution where the client and server can both process the same front-end code and generate the same user interface.
现在,我们不一定要坚持只做客户端或只做服务器端的解决方案。一个更好的方法是:客户端和服务器都能处理相同的前端代码并生成相同的用户界面的解决方案。
There are benefits to this approach, which we’ll discuss later.
这种方法有一些好处,我们将在后面讨论。
Such web applications are called Isomorphic or Universal. Now the client-side language is most exclusively JavaScript. Hence, for an isomorphic app to work, we have to use JavaScript at the server-side as well.
这样的网络应用被称为同构或通用。现在,客户端的语言最主要的是JavaScript。因此,为了使同构应用程序发挥作用,我们必须在服务器端也使用JavaScript。
Node.js is by far the most common choice to build a server-side rendered application.
到目前为止,Node.js是构建服务器端渲染应用程序的最常见选择。
4. What Is Nashorn?
4.什么是Nashorn?
So, where does Nashorn fit in, and why should we use it? Nashorn is a JavaScript engine packaged by default with Java. Hence, if we already have a web application back-end in Java and want to build an isomorphic app, Nashorn is pretty handy!
那么,Nashorn的作用在哪里,我们为什么要使用它?Nashorn是一个默认与Java打包的JavaScript引擎。因此,如果我们已经有了一个Java的Web应用后端,并希望构建一个同构的应用,那么Nashorn就非常方便了</a
Nashorn has been released as part of Java 8. This is primarily focused on allowing embedded JavaScript applications in Java.
Nashorn已经作为Java 8的一部分发布。这主要侧重于允许在Java中嵌入JavaScript应用程序。
Nashorn compiles JavaScript in-memory to Java Bytecode and passes it to the JVM for execution. This offers better performance compared to the earlier engine, Rhino.
Nashorn 在内存中将JavaScript编译为Java字节码,并将其传递给JVM执行。与早期的引擎Rhino相比,这提供了更好的性能。
5. Creating an Isomorphic App
5.创建一个同构的应用程序
We have gone through enough context now. Our application here will display a Fibonacci sequence and provide a button to generate and display the next number in the sequence. Let’s create a simple isomorphic app now with a back-end and front-end:
我们现在已经经历了足够的背景。我们的应用程序将显示一个斐波那契数列,并提供一个按钮来生成和显示数列中的下一个数字。现在让我们创建一个简单的具有后端和前端的同构应用程序。
- Front-end: A simple React.js based front-end
- Back-end: A simple Spring Boot back-end with Nashorn to process JavaScript
6. Application Front-End
6.前端应用
We’ll be using React.js for creating our front end. React is a popular JavaScript library for building single-page apps. It helps us decompose a complex user interface into hierarchical components with optional state and one-way data binding.
我们将使用React.js来创建我们的前端。React是一个流行的JavaScript库,用于构建单页应用程序。它帮助我们将复杂的用户界面分解成具有可选的状态和单向数据绑定的分层组件。
React parses this hierarchy and creates an in-memory data structure called virtual DOM. This helps React to find changes between different states and make minimal changes to the browser DOM.
React解析这个层次结构并创建一个叫做虚拟DOM的内存数据结构。这有助于React找到不同状态之间的变化,并对浏览器DOM做出最小的改变。
6.1. React Component
6.1.React组件
Let’s create our first React component:
让我们来创建我们的第一个React组件。
var App = React.createClass({displayName: "App",
handleSubmit: function() {
var last = this.state.data[this.state.data.length-1];
var secondLast = this.state.data[this.state.data.length-2];
$.ajax({
url: '/next/'+last+'/'+secondLast,
dataType: 'text',
success: function(msg) {
var series = this.state.data;
series.push(msg);
this.setState({data: series});
}.bind(this),
error: function(xhr, status, err) {
console.error('/next', status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.setState({data: this.props.data});
},
getInitialState: function() {
return {data: []};
},
render: function() {
return (
React.createElement("div", {className: "app"},
React.createElement("h2", null, "Fibonacci Generator"),
React.createElement("h2", null, this.state.data.toString()),
React.createElement("input", {type: "submit", value: "Next", onClick: this.handleSubmit})
)
);
}
});
Now, let’s understand what is the above code doing:
现在,让我们了解一下上面的代码在做什么。
- To begin with, we have defined a class component in React called “App”
- The most important function inside this component is “render”, which is responsible for generating the user interface
- We have provided a style className that the component can use
- We’re making use of the component state here to store and display the series
- While the state initializes as an empty list, it fetches data passed to the component as a prop when the component mounts
- Finally, on click of the button “Add”, a jQuery call to the REST service is made
- The call fetches the next number in the sequence and appends it to the component’s state
- Change in the component’s state automatically re-renders the component
6.2. Using the React Component
6.2.使用React组件
React looks for a named “div” element in the HTML page to anchor its contents. All we have to do is provide an HTML page with this “div” element and load the JS files:
React在HTML页面中寻找一个名为 “div “的元素来锚定其内容。我们所要做的就是提供一个带有这个 “div “元素的HTML页面,并加载JS文件。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello React</title>
<script type="text/javascript" src="js/react.js"></script>
<script type="text/javascript" src="js/react-dom.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
ReactDOM.render(
React.createElement(App, {data: [0,1,1]}),
document.getElementById("root")
);
</script>
</body>
</html>
So, let’s see what we’ve done here:
所以,让我们看看我们在这里做了什么。
- We imported the required JS libraries, react, react-dom and jQuery
- After that, we defined a “div” element called “root”
- We also imported the JS file with our React component
- Next, we called the React component “App” with some seed data, the first three Fibonacci numbers
7. Application Back-End
7.应用程序后端
Now, let’s see how we can create a fitting back-end for our application. We’ve already decided to use Spring Boot along with Spring Web for building this application. More importantly, we’ve decided to use Nashorn to process the JavaScript-based front-end we developed in the last section.
现在,让我们看看如何为我们的应用程序创建一个合适的后端。我们已经决定使用Spring Boot和Spring Web来构建这个应用程序。更重要的是,我们已经决定使用Nashorn来处理我们在上一节开发的基于JavaScript的前端。
7.1. Maven Dependencies
7.1.Maven的依赖性
For our simple application, we’ll be using JSP together with Spring MVC, so we’ll add a couple of dependencies to our POM:
对于我们的简单应用,我们将使用JSP和Spring MVC,所以我们将在POM中添加几个依赖项。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
The first one is the standard spring boot dependency for a web application. The second one is needed to compile JSPs.
第一个是Web应用程序的标准spring boot依赖性。第二个是编译JSPs所需的。
7.2. Web Controller
7.2. 网络控制器
Let’s now create our web controller, which will process our JavaScript file and return an HTML using JSP:
现在让我们创建我们的Web控制器,它将处理我们的JavaScript文件并使用JSP返回一个HTML。
@Controller
public class MyWebController {
@RequestMapping("/")
public String index(Map<String, Object> model) throws Exception {
ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
nashorn.eval(new FileReader("static/js/react.js"));
nashorn.eval(new FileReader("static/js/react-dom-server.js"));
nashorn.eval(new FileReader("static/app.js"));
Object html = nashorn.eval(
"ReactDOMServer.renderToString(" +
"React.createElement(App, {data: [0,1,1]})" +
");");
model.put("content", String.valueOf(html));
return "index";
}
}
So, what exactly is happening here:
那么,这里到底发生了什么。
- We fetch an instance of ScriptEngine of type Nashorn from ScriptEngineManager
- Then, we load relevant libraries to React, react.js, and react-dom-server.js
- We also load our JS file that has our react component “App”
- Finally, we evaluate a JS fragment creating react element with the component “App” and some seed data
- This provides us with an output of React, an HTML fragment as Object
- We pass this HTML fragment as data to the relevant view – the JSP
7.3. JSP
7.3 JSP
Now, how do we process this HTML fragment in our JSP?
现在,我们如何在我们的JSP中处理这个HTML片段?
Recall that React automatically adds its output to a named “div” element – “root” in our case. However, we’ll add our server-side generated HTML fragment to the same element manually in our JSP.
回想一下,React会自动将其输出添加到一个名为 “div “的元素中–在我们的例子中是 “root”。然而,我们将在JSP中手动将服务器端生成的HTML片段添加到同一个元素中。
Let’s see how the JSP looks now:
让我们看看JSP现在看起来如何。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello React!</title>
<script type="text/javascript" src="js/react.js"></script>
<script type="text/javascript" src="js/react-dom.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="root">${content}</div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
ReactDOM.render(
React.createElement(App, {data: [0,1,1]}),
document.getElementById("root")
);
</script>
</body>
</html>
This is the same page we created earlier, except for the fact that we’ve added our HTML fragment into the “root” div, which was empty earlier.
这是我们之前创建的同一个页面,只是我们将HTML片段添加到了 “根 “DIV中,而之前这个DIV是空的。
7.4. REST Controller
7.4.REST控制器
Finally, we also need a server-side REST endpoint that gives us the next Fibonacci number in the sequence:
最后,我们还需要一个服务器端的REST端点,为我们提供序列中的下一个斐波那契数字。
@RestController
public class MyRestController {
@RequestMapping("/next/{last}/{secondLast}")
public int index(
@PathVariable("last") int last,
@PathVariable("secondLast") int secondLast) throws Exception {
return last + secondLast;
}
}
Nothing fancy here, just a simple Spring REST controller.
这里没有什么花哨的,只是一个简单的Spring REST控制器。
8. Running the Application
8.运行应用程序
Now, that we have completed our front-end as well as our back-end, it’s time to run the application.
现在,我们已经完成了我们的前端以及后端,是时候运行这个应用程序了。
We should start the Spring Boot application normally, making use of the bootstrapping class:
我们应该正常启动Spring Boot应用程序,利用bootstrapping类。
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
When we run this class, Spring Boot compiles our JSPs and makes them available on embedded Tomcat along with the rest of the web application.
当我们运行这个类时,Spring Boot会编译我们的JSP,并使它们与Web应用程序的其他部分一起在嵌入式Tomcat上可用。
Now, if we visit our site, we’ll see:
现在,如果我们访问我们的网站,我们会看到。
Let’s understand the sequence of events:
让我们了解一下事件的顺序。
- The browser requests this page
- When the request for this page arrives, Spring web controller process the JS files
- Nashorn engine generates an HTML fragment and passes this to the JSP
- JSP adds this HTML fragment to the “root” div element, finally returning the above HTML page
- The browser renders the HTML, meanwhile starts downloading JS files
- Finally, the page is ready for client-side actions — we can add more numbers in the series
The important thing to understand here is what happens if React finds an HTML fragment in the target “div” element. In such cases, React compares this fragment with what it has and does not replace it if it finds a legible fragment. This is exactly what powers server-side rendering and isomorphic apps.
这里需要理解的是,如果React在目标 “div “元素中发现一个HTML片段,会发生什么。在这种情况下,React会将这个片段与它所拥有的片段进行比较,如果发现一个可读的片段,则不会将其替换掉。这正是服务器端渲染和同构应用程序的动力所在。
9. What More Is Possible?
9.还有什么是可能的?
In our simple example, we have just scratched the surface of what’s possible. Front-end applications with modern JS-based frameworks are getting increasingly more powerful and complex. With this added complexity, there are many things that we need to take care of:
在我们的简单例子中,我们只是触及了可能的表面。使用现代基于JS的框架的前端应用程序正变得越来越强大和复杂。随着这种复杂性的增加,有许多事情需要我们去处理。
- We’ve created just one React component in our application when in reality, this can be several components forming a hierarchy which pass data through props
- We would like to create separate JS files for every component to keep them manageable and manage their dependencies through “exports/require” or “export/import”
- Moreover, it may not be possible to manage state within components only; we may want to use a state management library like Redux
- Furthermore, we may have to interact with external services as side-effects of actions; this may require us to use a pattern like redux-thunk or Redux-Saga
- Most importantly, we would want to leverage JSX, a syntax extension to JS for describing the user interface
While Nashorn is fully compatible with pure JS, it may not support all the features mentioned above. Many of these require trans-compiling and polyfills due to JS compatibility.
虽然Nashorn与纯JS完全兼容,但它可能不支持上述的所有功能。由于JS的兼容性,其中许多需要反编译和polyfills。
The usual practice in such cases is to leverage a module bundler like Webpack or Rollup. What they mainly do is to process all of React source files and bundle them into a single JS file along with all dependencies. This invariably requires a modern JavaScript compiler like Babel to compile JavaScript to be backward compatible.
在这种情况下,通常的做法是利用模块捆绑器,如Webpack或Rollup。它们的主要作用是处理所有的React源文件,并将它们与所有的依赖关系一起捆绑成一个JS文件。这无一例外地需要一个现代的JavaScript编译器,如Babel来编译JavaScript,以便向后兼容。
The final bundle only has good old JS, which browsers can understand and Nashorn adheres to as well.
最后的捆绑包只有好的旧JS,浏览器可以理解,Nashorn也遵守。
10. Benefits of an Isomorphic App
10.同构式应用程序的好处
So, we’ve talked a great deal about isomorphic apps and have even created a simple application now. But why exactly should we even care about this? Let’s understand some of the key benefits of using an isomorphic app.
所以,我们已经谈了很多关于同构应用程序的内容,甚至现在已经创建了一个简单的应用程序。但是,我们到底为什么要关心这个问题呢?让我们了解一下使用同构应用程序的一些主要好处。
10.1. First Page Rendering
10.1.第一页的渲染
One of the most significant benefits of an isomorphic app is the faster rendering of the first page. In the typical client-side rendered application, the browser begins by downloading all the JS and CSS artifacts.
同构应用程序最显著的好处之一是更快地渲染第一页。在典型的客户端渲染应用程序中,浏览器首先要下载所有的JS和CSS构件。
After that, they load and start rendering the first page. If we send the first page rendered from the server-side, this can be much faster, providing an enhanced user experience.
之后,他们加载并开始渲染第一个页面。如果我们从服务器端发送渲染的第一个页面,这可以快得多,提供一个增强的用户体验。
10.2. SEO Friendly
10.2 SEO友好
Another benefit often cited with server-side rendering is related to SEO. It’s believed that search bots are not able to process JavaScript and hence do not see an index page rendered at client-side through libraries like React. A server-side rendered page, therefore, is SEO friendlier. It’s worth noting, though, that Modern search engine bots claim to process JavaScript.
另一个经常被引用的服务器端渲染的好处是与SEO有关的。人们认为,搜索机器人无法处理JavaScript,因此不会看到通过React等库在客户端渲染的索引页面。因此,服务器端渲染的页面对SEO更友好。不过,值得注意的是,现代搜索引擎机器人声称可以处理JavaScript。
11. Conclusion
11.结语
In this tutorial, we went through the basic concepts of isomorphic applications and the Nashorn JavaScript engine. We further explored how to build an isomorphic app with Spring Boot, React, and Nashorn.
在本教程中,我们了解了同构应用程序的基本概念和Nashorn JavaScript引擎。我们进一步探讨了如何用Spring Boot、React和Nashorn构建一个同构应用程序。
Then, we discussed the other possibilities to extend the front-end application and the benefits of using an isomorphic app.
然后,我们讨论了扩展前端应用程序的其他可能性以及使用同构应用程序的好处。
As always, the code can be found over on GitHub.
一如既往,代码可以在GitHub上找到over。