1. Gambaran keseluruhan
Dalam tutorial ini, kita akan memahami apa sebenarnya aplikasi isomorfik. Kami juga akan membincangkan Nashorn, mesin JavaScript yang digabungkan dengan Java.
Selanjutnya, kita akan meneroka bagaimana kita dapat menggunakan Nashorn bersama dengan perpustakaan depan seperti React untuk membuat aplikasi isomorfik.
2. Sedikit Sejarah
Secara tradisional, aplikasi pelanggan dan pelayan ditulis dengan cara yang agak berat di sisi pelayan. Fikirkan PHP sebagai mesin skrip yang menghasilkan HTML dan penyemak imbas web yang statik menjadikannya.
Netscape hadir dengan sokongan JavaScript dengan penyemak imbasnya pada pertengahan tahun sembilan puluhan . Itu mula mengalihkan sebahagian pemprosesan dari pelayan ke pelayar sisi pelanggan. Untuk masa yang lama, pembangun menghadapi masalah yang berbeza mengenai sokongan JavaScript di penyemak imbas web.
Dengan permintaan yang semakin meningkat untuk pengalaman pengguna yang lebih cepat dan interaktif, batasan itu sudah didorong lebih keras. Salah satu kerangka terawal yang mengubah permainan adalah jQuery. Ia membawa beberapa fungsi mesra pengguna dan sokongan yang lebih baik untuk AJAX.
Tidak lama kemudian, banyak kerangka untuk pengembangan front-end mulai muncul, yang meningkatkan pengalaman pemaju. Bermula dengan AngularJS dari Google, React dari Facebook, dan kemudian, Vue, mereka mula menarik perhatian pembangun.
Dengan sokongan penyemak imbas moden, kerangka kerja yang luar biasa dan alat yang diperlukan, arus sebahagian besarnya beralih ke sisi pelanggan .
Pengalaman yang mengasyikkan pada peranti genggam yang semakin pantas memerlukan pemprosesan di sisi pelanggan yang lebih banyak.
3. Apa itu Aplikasi Isomorf?
Oleh itu, kami melihat bagaimana kerangka depan dapat membantu kami mengembangkan aplikasi web di mana antara muka pengguna diberikan sepenuhnya di sisi pelanggan.
Walau bagaimanapun, mungkin juga menggunakan kerangka yang sama di sisi pelayan dan menghasilkan antara muka pengguna yang sama.
Sekarang, kita tidak perlu berpegang pada penyelesaian pelanggan sahaja atau penyelesaian hanya dari sisi pelayan. Cara yang lebih baik adalah mempunyai penyelesaian di mana klien dan pelayan dapat memproses kod front-end yang sama dan menghasilkan antara muka pengguna yang sama.
Terdapat faedah untuk pendekatan ini, yang akan kita bincangkan kemudian.

Aplikasi web seperti itu disebut Isomorphic atau Universal . Kini bahasa sisi pelanggan adalah JavaScript secara eksklusif. Oleh itu, agar aplikasi isomorfik berfungsi, kita juga harus menggunakan JavaScript di bahagian pelayan.
Node.js adalah pilihan yang paling biasa untuk membina aplikasi yang dibuat di sisi pelayan.
4. Apa itu Nashorn?
Jadi, di mana Nashorn sesuai, dan mengapa kita harus menggunakannya? Nashorn adalah mesin JavaScript yang dikemas secara lalai dengan Java . Oleh itu, jika kita sudah mempunyai aplikasi web back-end di Java dan ingin membuat aplikasi isomorfik, Nashorn cukup berguna!
Nashorn telah diluncurkan sebagai bagian dari Java 8. Ini terutama difokuskan untuk memungkinkan aplikasi JavaScript tertanam di Java.
Nashorn menyusun JavaScript dalam memori ke Java Bytecode dan menyebarkannya ke JVM untuk pelaksanaan. Ini menawarkan prestasi yang lebih baik berbanding dengan mesin sebelumnya, Rhino.
5. Membuat Aplikasi Isomorf
Kami telah melalui konteks yang cukup sekarang. Aplikasi kami di sini akan memaparkan urutan Fibonacci dan menyediakan butang untuk menghasilkan dan memaparkan nombor seterusnya dalam urutan. Mari buat aplikasi isomorfik ringkas sekarang dengan back-end dan front-end:
- Bahagian depan: Bahagian depan React.js yang ringkas
- Back-end: Back-spring Spring yang ringkas dengan Nashorn untuk memproses JavaScript
6. Permohonan Bahagian Depan
Kami akan menggunakan React.js untuk mencipta bahagian depan kami . React adalah perpustakaan JavaScript yang popular untuk membina aplikasi satu halaman. Ini membantu kita menguraikan antara muka pengguna yang kompleks menjadi komponen hierarki dengan keadaan pilihan dan pengikatan data sehala.
React menguraikan hierarki ini dan mewujudkan struktur data dalam memori yang disebut virtual DOM. Ini membantu React untuk mencari perubahan antara keadaan yang berbeza dan membuat perubahan minimum pada DOM penyemak imbas.
6.1. Reaksi Komponen
Mari buat komponen React pertama kami:
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}) ) ); } });
Sekarang, mari kita fahami apa yang dilakukan kod di atas:
- Sebagai permulaan, kami telah menentukan komponen kelas dalam React yang disebut "Aplikasi"
- Fungsi yang paling penting dalam komponen ini adalah "render" , yang bertanggungjawab untuk menghasilkan antara muka pengguna
- Kami telah menyediakan className gaya yang boleh digunakan oleh komponen tersebut
- Kami menggunakan keadaan komponen di sini untuk menyimpan dan memaparkan siri ini
- Walaupun keadaan diinisialisasi sebagai daftar kosong, ia mengambil data yang dikirimkan ke komponen sebagai alat penyangga ketika komponen dipasang
- Akhirnya, pada klik butang "Tambah", panggilan jQuery ke perkhidmatan REST dibuat
- Panggilan mengambil nombor seterusnya dalam urutan dan menambahkannya ke keadaan komponen
- Perubahan dalam keadaan komponen secara automatik memberikan komponen semula
6.2. Menggunakan Komponen React
React mencari elemen "div" bernama di halaman HTML untuk meletakkan kandungannya . Yang harus kita lakukan hanyalah menyediakan halaman HTML dengan elemen "div" ini dan memuatkan fail JS:
Hello React ReactDOM.render( React.createElement(App, {data: [0,1,1]}), document.getElementById("root") );
Oleh itu, mari kita lihat apa yang telah kita lakukan di sini:
- 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
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.
7.1. Maven Dependencies
For our simple application, we'll be using JSP together with Spring MVC, so we'll add a couple of dependencies to our POM:
org.springframework.boot spring-boot-starter-web org.apache.tomcat.embed tomcat-embed-jasper provided
The first one is the standard spring boot dependency for a web application. The second one is needed to compile JSPs.
7.2. Web Controller
Let's now create our web controller, which will process our JavaScript file and return an HTML using JSP:
@Controller public class MyWebController { @RequestMapping("/") public String index(Map 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
Now, how do we process this HTML fragment in our JSP?
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.
Let's see how the JSP looks now:
Hello React! ${content} ReactDOM.render( React.createElement(App, {data: [0,1,1]}), document.getElementById("root") );
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.
7.4. REST Controller
Finally, we also need a server-side REST endpoint that gives us the next Fibonacci number in the sequence:
@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.
8. Running the Application
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:
@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.
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.
9. What More Is Possible?
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:
- 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.
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.
The final bundle only has good old JS, which browsers can understand and Nashorn adheres to as well.
10. Benefits of an Isomorphic App
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
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.
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
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.
11. Kesimpulannya
Dalam tutorial ini, kami membahas konsep asas aplikasi isomorfik dan mesin JavaScript Nashorn. Kami seterusnya meneroka cara membina aplikasi isomorfik dengan Spring Boot, React, dan Nashorn.
Kemudian, kami membincangkan kemungkinan lain untuk memperluas aplikasi front-end dan faedah menggunakan aplikasi isomorfik.
Seperti biasa, kodnya boleh didapati di GitHub.