Isomorfe applicatie met React en Nashorn

1. Overzicht

In deze tutorial zullen we begrijpen wat precies een isomorfe app is. We zullen ook Nashorn bespreken, de JavaScript-engine die met Java is gebundeld.

Verder zullen we onderzoeken hoe we Nashorn samen met een front-end bibliotheek zoals React kunnen gebruiken om een ​​isomorfe app te maken.

2. Een beetje geschiedenis

Traditioneel werden client- en servertoepassingen geschreven op een manier die behoorlijk zwaar was op de server. Beschouw PHP als een script-engine die voornamelijk statische HTML genereert en webbrowsers die deze weergeven.

Netscape kwam met de ondersteuning van JavaScript in zijn browser halverwege de jaren negentig. Dat begon een deel van de verwerking te verschuiven van de server-side naar de client-side browser. Ontwikkelaars worstelden lange tijd met verschillende problemen met betrekking tot JavaScript-ondersteuning in webbrowsers.

Met de groeiende vraag naar snellere en interactieve gebruikerservaring werd de grens al harder verlegd. Een van de eerste frameworks die het spel veranderde, was jQuery. Het bracht verschillende gebruiksvriendelijke functies en sterk verbeterde ondersteuning voor AJAX.

Al snel verschenen er veel frameworks voor front-end ontwikkeling, wat de ervaring van de ontwikkelaar enorm verbeterde. Beginnend met AngularJS van Google, React van Facebook en later, Vue, begonnen ze de aandacht van ontwikkelaars te trekken.

Met moderne browserondersteuning, opmerkelijke frameworks en vereiste tools, de getijden verschuiven grotendeels naar de kant van de klant.

Een meeslepende ervaring op steeds snellere draagbare apparaten vereist meer verwerking aan de clientzijde.

3. Wat is een isomorfe app?

We hebben dus gezien hoe front-end frameworks ons helpen een webapplicatie te ontwikkelen waarbij de gebruikersinterface volledig aan de clientzijde wordt weergegeven.

Echter, Het is ook mogelijk om hetzelfde framework aan de serverzijde te gebruiken en genereer dezelfde gebruikersinterface.

Nu hoeven we niet per se vast te houden aan oplossingen aan de clientzijde of alleen aan de serverzijde. Een betere manier is om een ​​oplossing te hebben waar de client en server kunnen beide dezelfde front-end code verwerken en genereer dezelfde gebruikersinterface.

Deze aanpak heeft voordelen, die we later zullen bespreken.

Dergelijke webapplicaties worden isomorf of universeel genoemd. Nu is de taal aan de clientzijde het meest exclusief JavaScript. Om een ​​isomorfe app te laten werken, moeten we dus ook JavaScript aan de serverzijde gebruiken.

Node.js is verreweg de meest gebruikelijke keuze om een ​​server-side gerenderde applicatie te bouwen.

4. Wat is Nashorn?

Dus, waar past Nashorn in, en waarom zouden we het gebruiken? Nashorn is een JavaScript-engine die standaard met Java is verpakt. Als we dus al een back-end van een webtoepassing in Java hebben en een isomorfe app willen bouwen, is Nashorn best handig!

Nashorn is uitgebracht als onderdeel van Java 8. Dit is voornamelijk gericht op het toestaan ​​van embedded JavaScript-toepassingen in Java.

Nashorn compileert JavaScript in-memory naar Java Bytecode en geeft het door aan de JVM voor uitvoering. Dit biedt betere prestaties in vergelijking met de eerdere motor, Rhino.

5. Een isomorfe app maken

We hebben nu genoeg context doorgenomen. Onze applicatie hier zal een Fibonacci-reeks weergeven en een knop bieden om het volgende nummer in de reeks te genereren en weer te geven. Laten we nu een eenvoudige isomorfe app maken met een back-end en front-end:

  • Front-end: een eenvoudige op React.js gebaseerde front-end
  • Back-end: een eenvoudige Spring Boot-back-end met Nashorn om JavaScript te verwerken

6. Applicatie front-end

We zullen gebruiken React.js voor het maken van onze front-end. React is een populaire JavaScript-bibliotheek voor het bouwen van apps met één pagina. Het helpt ons een complexe gebruikersinterface op te splitsen in hiërarchische componenten met optionele status- en eenrichtingsgegevensbinding.

React parseert deze hiërarchie en creëert een datastructuur in het geheugen die virtuele DOM wordt genoemd. Dit helpt React om veranderingen tussen verschillende statussen te vinden en minimale wijzigingen aan te brengen in de browser-DOM.

6.1. Reageer Component

Laten we onze eerste React-component maken:

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 () {retour {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})));}} );

Laten we nu eens kijken wat de bovenstaande code doet:

  • Om te beginnen hebben we een klassecomponent gedefinieerd in React genaamd 'App'
  • Het meest belangrijke functie binnen deze component is "render", die verantwoordelijk is voor het genereren van de gebruikersinterface
  • We hebben gezorgd voor een stijl naam van de klasse die de component kan gebruiken
  • We maken hier gebruik van de componentstatus om de serie op te slaan en weer te geven
  • Terwijl de status wordt geïnitialiseerd als een lege lijst, haalt het gegevens op die als een prop aan de component zijn doorgegeven wanneer de component wordt geactiveerd
  • Ten slotte, bij een klik op de knop "Toevoegen", wordt een jQuery-aanroep naar de REST-service gemaakt
  • De oproep haalt het volgende nummer in de reeks op en voegt dit toe aan de status van de component
  • Verandering in de staat van de component geeft de component automatisch opnieuw weer

6.2. De React-component gebruiken

React zoekt een genoemd "div" -element in de HTML-pagina om de inhoud ervan te verankeren. Het enige wat we hoeven te doen is een HTML-pagina te voorzien van dit "div" -element en de JS-bestanden te laden:

   Hallo React ReactDOM.render (React.createElement (App, {data: [0,1,1]}), document.getElementById ("root")); 

Laten we dus eens kijken wat we hier hebben gedaan:

  • Wij importeerde de vereiste JS-bibliotheken, react, react-dom en jQuery
  • Daarna hebben we een 'div'-element gedefinieerd met de naam' root '
  • We hebben ook het JS-bestand geïmporteerd met onze React-component
  • Vervolgens noemden we de React-component "App" met wat seed-gegevens, de eerste drie Fibonacci-getallen

7. Applicatie-back-end

Laten we nu eens kijken hoe we een passende back-end voor onze applicatie kunnen maken. We hebben al besloten om gebruik Spring Boot samen met Spring Web voor het bouwen van deze applicatie. Wat nog belangrijker is, we hebben besloten om gebruik Nashorn om de op JavaScript gebaseerde front-end te verwerken we hebben ontwikkeld in de laatste sectie.

7.1. Afhankelijkheden van Maven

Voor onze eenvoudige applicatie gebruiken we JSP samen met Spring MVC, dus we zullen een aantal afhankelijkheden aan onze POM toevoegen:

 org.springframework.boot spring-boot-starter-web org.apache.tomcat.embed tomcat-embed-jasper voorzien 

De eerste is de standaard afhankelijkheid van het opstarten van een webtoepassing. De tweede is nodig om JSP's te compileren.

7.2. Webcontroller

Laten we nu onze webcontroller maken, die ons JavaScript-bestand verwerkt en een HTML retourneert met behulp van JSP:

@Controller openbare klasse MyWebController {@RequestMapping ("/") openbare String-index (kaartmodel) genereert uitzondering {ScriptEngine nashorn = nieuwe ScriptEngineManager (). GetEngineByName ("nashorn"); nashorn.eval (nieuwe FileReader ("static / js / react.js")); nashorn.eval (nieuwe FileReader ("static / js / react-dom-server.js")); nashorn.eval (nieuwe FileReader ("static / app.js")); Object html = nashorn.eval ("ReactDOMServer.renderToString (" + "React.createElement (App, {data: [0,1,1]})" + ");"); model.put ("content", String.valueOf (html)); retourneer "index"; }}

Dus wat gebeurt er hier precies:

  • We halen een exemplaar op van ScriptEngine type Nashorn van ScriptEngineManager
  • Dan gaan we laad relevante bibliotheken in React, react.js en react-dom-server.js
  • We laden ook ons ​​JS-bestand met onze reactiecomponent "App"
  • Ten slotte evalueren we een JS-fragment dat een reactie-element creëert met de component "App" en enkele seed-gegevens
  • Dit geeft ons een output van React, een HTML-fragment als Voorwerp
  • We geven dit HTML-fragment als data door aan de relevante view - de JSP

7.3. JSP

Hoe verwerken we dit HTML-fragment nu in onze JSP?

Bedenk dat React automatisch zijn uitvoer toevoegt aan een genaamd "div" -element - "root" in ons geval. Echter, we zullen ons server-side gegenereerde HTML-fragment handmatig aan hetzelfde element toevoegen in onze JSP.

Laten we eens kijken hoe de JSP er nu uitziet:

   Hallo Reageer! $ {content} ReactDOM.render (React.createElement (App, {data: [0,1,1]}), document.getElementById ("root")); 

Dit is dezelfde pagina die we eerder hebben gemaakt, behalve dat we ons HTML-fragment hebben toegevoegd aan de "root" -div, die eerder leeg was.

7.4. REST-controller

Ten slotte hebben we ook een REST-eindpunt aan de serverzijde nodig dat ons het volgende Fibonacci-nummer in de reeks geeft:

@RestController openbare klasse MyRestController {@RequestMapping ("/ next / {last} / {secondLast}") openbare int index (@PathVariable ("last") int last, @PathVariable ("secondLast") int secondLast) gooit Uitzondering {return laatste + tweedeLaatste; }}

Niets bijzonders hier, alleen een simpele Spring REST-controller.

8. De toepassing uitvoeren

Nu we zowel onze front-end als onze back-end hebben voltooid, is het tijd om de applicatie uit te voeren.

We zouden de Spring Boot-applicatie normaal moeten starten, gebruikmakend van de bootstrapping-klasse:

@SpringBootApplication openbare klasse Applicatie breidt SpringBootServletInitializer uit {@Override beschermde SpringApplicationBuilder-configuratie (SpringApplicationBuilder-applicatie) {return application.sources (Application.class); } public static void main (String [] args) gooit Uitzondering {SpringApplication.run (Application.class, args); }}

Als we deze les geven, Spring Boot compileert onze JSP's en stelt ze beschikbaar op embedded Tomcat samen met de rest van de webapplicatie.

Als we nu onze site bezoeken, zien we:

Laten we de opeenvolging van gebeurtenissen begrijpen:

  • De browser vraagt ​​deze pagina op
  • Wanneer het verzoek voor deze pagina binnenkomt, verwerkt Spring-webcontroller de JS-bestanden
  • De Nashorn-engine genereert een HTML-fragment en geeft dit door aan de JSP
  • JSP voegt dit HTML-fragment toe aan het div-element "root" en retourneert uiteindelijk de bovenstaande HTML-pagina
  • De browser rendert de HTML en begint ondertussen JS-bestanden te downloaden
  • Eindelijk is de pagina klaar voor acties aan de clientzijde - we kunnen meer nummers in de reeks toevoegen

Het belangrijkste om hier te begrijpen is wat er gebeurt als React een HTML-fragment vindt in het doelelement "div". In dergelijke gevallen, React vergelijkt dit fragment met wat het heeft en vervangt het niet als het een leesbaar fragment vindt. Dit is precies wat server-side rendering en isomorfe apps mogelijk maakt.

9. Wat is er nog meer mogelijk?

In ons eenvoudige voorbeeld hebben we zojuist de oppervlakte bekrast van wat mogelijk is. Front-end applicaties met moderne JS-gebaseerde frameworks worden steeds krachtiger en complexer. Met deze extra complexiteit zijn er veel dingen waar we voor moeten zorgen:

  • We hebben slechts één React-component in onze applicatie gemaakt, terwijl dit in werkelijkheid kan zijn verschillende componenten die een hiërarchie vormen die gegevens doorgeven via rekwisieten
  • Wij zouden graag willen maak afzonderlijke JS-bestanden voor elk onderdeel om ze beheersbaar te houden en hun afhankelijkheden te beheren via "exporteren / vereisen" of "exporteren / importeren"
  • Bovendien is het wellicht niet mogelijk om de status alleen binnen componenten te beheren; we willen misschien gebruiken een state management-bibliotheek zoals Redux
  • Bovendien moeten we mogelijk interactie hebben met externe diensten als neveneffecten van acties; dit kan ons nodig hebben om te gebruiken een patroon als redux-thunk of Redux-Saga
  • Het belangrijkste is dat we dat zouden willen gebruik JSX, een syntaxisuitbreiding voor JS voor het beschrijven van de gebruikersinterface

Hoewel Nashorn volledig compatibel is met pure JS, ondersteunt het mogelijk niet alle bovengenoemde functies. Veel van deze vereisen transcompilatie en polyfills vanwege JS-compatibiliteit.

De gebruikelijke praktijk in dergelijke gevallen is om gebruik maken van een modulebundler zoals Webpack of Rollup. Wat ze voornamelijk doen, is alle React-bronbestanden verwerken en ze samen met alle afhankelijkheden bundelen in een enkel JS-bestand. Dit vereist steevast een moderne JavaScript-compiler zoals Babel om JavaScript te compileren om achterwaarts compatibel te zijn.

De laatste bundel bevat alleen goede oude JS, die browsers kunnen begrijpen en ook Nashorn houdt zich eraan.

10. Voordelen van een isomorfe app

We hebben het dus veel gehad over isomorfe apps en hebben nu zelfs een eenvoudige applicatie gemaakt. Maar waarom zouden we hier eigenlijk eigenlijk om geven? Laten we enkele van de belangrijkste voordelen van het gebruik van een isomorfe app begrijpen.

10.1. Rendering van de eerste pagina

Een van de belangrijkste voordelen van een isomorfe app is de snellere weergave van de eerste pagina. In de typische client-side gerenderde applicatie begint de browser met het downloaden van alle JS- en CSS-artefacten.

Daarna laden ze en beginnen ze met het renderen van de eerste pagina. Als we de eerste pagina verzenden die wordt weergegeven vanaf de server, kan dit veel sneller zijn, wat een verbeterde gebruikerservaring oplevert.

10.2. SEO vriendelijk

Een ander voordeel dat vaak wordt aangehaald bij server-side rendering is gerelateerd aan SEO. Er wordt aangenomen dat zoekrobots JavaScript niet kunnen verwerken en daarom geen indexpagina zien die aan de clientzijde wordt weergegeven via bibliotheken zoals React. Een server-side gerenderde pagina is daarom SEO-vriendelijker. Het is echter vermeldenswaard dat moderne bots van zoekmachines beweren JavaScript te verwerken.

11. Conclusie

In deze tutorial hebben we de basisconcepten van isomorfe applicaties en de Nashorn JavaScript-engine doorgenomen. We hebben verder onderzocht hoe we een isomorfe app kunnen bouwen met Spring Boot, React en Nashorn.

Vervolgens bespraken we de andere mogelijkheden om de front-end applicatie uit te breiden en de voordelen van het gebruik van een isomorfe app.

Zoals altijd is de code te vinden op GitHub.