Isomorphic JavaScript: The Future of Web Apps

Isomorphic JavaScript: The Future of Web Apps

At Airbnb, we’ve learned a lot over the past few years while building rich web experiences. We dove into the single-page app world in 2011 with our mobile web site, and have since launched Wish Lists and our newly-redesigned search page, among others. Each of these is a large JavaScript app, meaning that the bulk of the code runs in the browser in order to support a more modern, interactive experience.

This approach is commonplace today, and libraries like Backbone.js, Ember.js, and Angular.js have made it easier for developers to build these rich JavaScript apps. We have found, however, that these types of apps have some critical limitations. To explain why, let’s first take a quick detour through the history of web apps.

JavaScript Grows Up

Since the dawn of the Web, the browsing experience has worked like this: a web browser would request a particular page (say, “http://www.geocities.com/”), causing a server somewhere on the Internet to generate an HTML page and send it back over the wire. This has worked well because browsers weren’t very powerful and HTML pages represented documents that were mostly static and self-contained. JavaScript, created to allow web pages to be more dynamic, didn’t enable much more than image slideshows and date picker widgets.

After years of advances in personal computing, creative technologists have pushed the web to its limits, and web browsers have evolved to keep up. Now, the Web has matured into a fully-featured application platform, and fast JavaScript runtimes and HTML5 standards have enabled developers to create the rich apps that before were only possible on native platforms.

The Single-Page App

It wasn’t long before developers started to build out entire applications in the browser using JavaScript, taking advantage of these new capabilities. Apps like Gmail, the classic example of the single-page app, could respond immediately to user interactions, no longer needing to make a round-trip to the server just to render a new page.

Libraries like Backbone.js, Ember.js, and Angular.js are often referred to as client-side MVC (Model-View-Controller) or MVVM (Model-View-ViewModel) libraries. The typical client-side MVC architecture looks something like this:

The bulk of the application logic (views, templates, controllers, models, internationalization, etc.) lives in the client, and it talks to an API for data. The server could be written in any language, such as Ruby, Python, or Java, and it mostly handles serving up an initial barebones page of HTML. Once the JavaScript files are downloaded by the browser, they are evaluated and the client-side app is initialized, fetching data from the API and rendering the rest of the HTML page.

This is great for the user because once the app is initially loaded, it can support quick navigation between pages without refreshing the page, and if done right, can even work offline.

This is great for the developer because the idealized single-page app has a clear separation of concerns between the client and the server, promoting a nice development workflow and preventing the need to share too much logic between the two, which are often written in different languages.

Trouble in Paradise

In practice, however, there are a few fatal flaws with this approach that prevent it from being right for many use cases.

SEO

An application that can only run in the client-side cannot serve HTML to crawlers, so it will have poor SEO by default. Web crawlers function by making a request to a web server and interpreting the result; but if the server returns a blank page, it’s not of much value. There are workarounds, but not without jumping through some hoops.

Performance

By the same token, if the server doesn’t render a full page of HTML but instead waits for client-side JavaScript to do so, users will experience a few critical seconds of blank page or loading spinner before seeing the content on the page. There are plenty of studies showing the drastic effect a slow site has on users, and thus revenue. Amazon claims that each 100ms reduction in page load time raises revenue by 1%. Twitter spent a year and 40 engineers rebuilding their site to render on the server instead of the client, claiming a 5x improvement in perceived loading time.

Maintainability

While the ideal case can lead to a nice, clean separation of concerns, inevitably some bits of application logic or view logic end up duplicated between client and server, often in different languages. Common examples are date and currency formatting, form validations, and routing logic. This makes maintenance a nightmare, especially for more complex apps.

Some developers, myself included, feel bitten by this approach — it’s often only after having invested the time and effort to build a single-page app that it becomes clear what the drawbacks are.

A Hybrid Approach

At the end of the day, we really want a hybrid of the new and old approaches: we want to serve fully-formed HTML from the server for performance and SEO, but we want the speed and flexibility of client-side application logic.

To this end, we’ve been experimenting at Airbnb with “Isomorphic JavaScript” apps, which are JavaScript applications that can run both on the client-side and the server-side.

An isomorphic app might look like this, dubbed here “Client-server MVC”:

In this world, some of your application and view logic can be executed on both the server and the client. This opens up all sorts of doors — performance optimizations, better maintainability, SEO-by-default, and more stateful web apps.

With Node.js, a fast, stable server-side JavaScript runtime, we can now make this dream a reality. By creating the appropriate abstractions, we can write our application logic such that it runs on both the server and the client — the definition of isomorphic JavaScript.

Isomorphic JavaScript in the Wild

This idea isn’t new — Nodejitsu wrote a great description of isomorphic JavaScript architecture in 2011 — but it’s been slow to adopt. There have been a few isomorphic frameworks to spring up already.

Mojito was the first open-source isomorphic framework to get any press. It’s an advanced, full-stack Node.js-based framework, but its dependence on YUI and Yahoo!-specific quirks haven’t led to much popularity in the JavaScript community since they open sourced it in April 2012.

Meteor is probably the most well-known isomorphic project today. Meteor is built from the ground up to support real-time apps, and the team is building an entire ecosystem around its package manager and deployment tools. Like Mojito, it is a large, opinionated Node.js framework, however it’s done a much better job engaging the JavaScript community, and its much-anticipated 1.0 release is just around the corner. Meteor is a project to keep tabs on — it’s got an all-star team, and it’s raised $11.2 M from Andreessen Horowitz — unheard of for a company wholly focused on releasing an open-source product.

Asana, the task management app founded by Facebook cofounder Dustin Moskovitz, has an interesting isomorphic story. Not hurting for funding, considering Moskovitz’ status as youngest billionaire in the world, Asana spent years in R&D developing their closed-source Luna framework, one of the most advanced examples of isomorphic JavaScript around. Luna, originally built on v8cgi in the days before Node.js existed, allows a complete copy of the app to run on the server for every single user session. It runs a separate server process for each user, executing the same JavaScript application code on the server that is running in the client, enabling a whole class of advanced optimizations, such as robust offline support and snappy real-time updates.

We launched an isomorphic library of our own earlier this year. Called Rendr, it allows you to build a Backbone.js + Handlebars.js single-page app that can also be fully rendered on the server-side. Rendr is a product of our experience rebuilding the Airbnb mobile web app to drastically improve pageload times, which is especially important for users on high-latency mobile connections. Rendr strives to be a library rather than a framework, so it solves fewer of the problems for you compared to Mojito or Meteor, but it is easy to modify and extend.

Abstraction, Abstraction, Abstraction

That these projects tend to be large, full-stack web frameworks speaks to the difficulty of the problem. The client and server are very dissimilar environments, and so we must create a set of abstractions that decouple our application logic from the underlying implementations, so we can expose a single API to the application developer.

Routing

We want a single set of routes that map URI patterns to route handlers. Our route handlers need to be able to access HTTP headers, cookies, and URI information, and specify redirects without directly accessing window.location (browser) or req and res (Node.js).

Fetching and persisting data

We want to describe the resources needed to render a particular page or component independently from the fetching mechanism. The resource descriptor could be a simple URI pointing to a JSON endpoint, or for larger applications, it may be useful to encapsulate resources in models and collections and specify a model class and primary key, which at some point would get translated to a URI.

View rendering

Whether we choose to directly manipulate the DOM, stick with string-based HTML templating, or opt for a UI component library with a DOM abstraction, we need to be able to generate markup isomorphically. We should be able to render any view on either the server or the client, dependent on the needs of our application.

Building and packaging

It turns out writing isomorphic application code is only half the battle. Tools like Grunt and Browserifyare essential parts of the workflow to actually get the app up and running. There can be a number of build steps: compiling templates, including client-side dependencies, applying transforms, minification, etc. The simple case is to combine all application code, views and templates into a single bundle, but for larger apps, this can result in hundreds of kilobytes to download. A more advanced approach is to create dynamic bundles and introduce asset lazy-loading, however this quickly gets complicated. Static-analysis tools like Esprima can allow ambitious developers to attempt advanced optimization andmetaprogramming to reduce boilerplate code.

Composing Together Small Modules

Being first to market with an isomorphic framework means you have to solve all these problems at once. But this leads to large, unwieldy frameworks that are hard to adopt and integrate into an already-existing app. As more developers tackle this problem, we’ll see an explosion of small, reusable modules that can be integrated together to build isomorphic apps.

It turns out that most JavaScript modules can already be used isomorphically with little to no modification. For example, popular libraries like Underscore, Backbone.js, Handlebars.js, Moment, and even jQuery can be used on the server.

To demonstrate this point, I’ve created a sample app called isomorphic-tutorial that you can check out on GitHub. By combining together a few modules, each that can be used isomorphically, it’s easy to create a simple isomorphic app in just a few hundred lines of code. It uses Director for server- and browser-based routing, Superagent for HTTP requests, and Handlebars.js for templating, all built on top of a basic Express.js app. Of course, as an app grows in complexity, one has to introduce more layers of abstraction, but my hope is that as more developers experiment with this, there will be new libraries and standards to emerge.

The View From Here

As more organizations get comfortable running Node.js in production, it’s inevitable that more and more web apps will begin to share code between their client and server code. It’s important to remember that isomorphic JavaScript is a spectrum — it can start with just sharing templates, progress to be an entire application’s view layer, all the way to the majority of the app’s business logic. Exactly what and how JavaScript code is shared between environments depends entirely on the application being built and its unique set of constraints.

Nicholas C. Zakas has a nice description of how he envisions apps will begin to pull their UI layer down to the server from the client, enabling performance and maintainability optimizations. An app doesn’t have to rip out its backend and replace it with Node.js to use isomorphic JavaScript, essentially throwing out the baby with the bathwater. Instead, by creating sensible APIs and RESTful resources, the traditional backend can live alongside the Node.js layer.

At Airbnb, we’ve already begun to retool our client-side build process to use Node.js-based tools like Grunt and Browserify. Our main Rails app may never be entirely supplanted by a Node.js app, but by embracing these tools it gets ever easier to share certain bits of JavaScript and templates between environments.

You heard it here first — within a few years, it will be rare to see an advanced web app that isn’t running some JavaScript on the server.

Learn More

If this idea excites you, come check out the Isomorphic JavaScript workshop I’ll be teaching at DevBeaton Tuesday, November 12 in San Francisco, or at General Assembly on Thursday, November 21. We’ll hack together on the sample Node.js isomorphic-tutorial app I’ve created to demonstrate how easy it really is to get started writing isomorphic apps.

Also keep tabs on the evolution of the Airbnb web apps by following me at @spikebrehm and the Airbnb Engineering team at @AirbnbNerds.

时间: 2024-10-09 10:20:00

Isomorphic JavaScript: The Future of Web Apps的相关文章

《HTML5 and Javascript Web Apps》读书笔记要点摘录

必须要承认的是这本由Wesley Hales编写的书对要进军web apps 的程序员(媛)来说绝对是福音,很薄的一本书简明扼要的说明了web apps的实现原理,实现工具以及优缺点.拾人牙慧,作此摘录: 1原理: 浏览器(即平台)已经称为应用程序的另一个平台,我们的前端代码现在打包为HTML5驱动的原生应用,扩展和操作系统.h5,Open Web和移动设备已经进一步推动了浏览器平台,使浏览器能够在离线状态下存储数据和运行应用. 2移动优先: 移动优先要求我们考虑代码质量.开发人员在使用css进

[WebView学习之六]:Web Apps最佳实践规则

上一篇我们学习了([WebView学习之五]:调试Web Apps),今天我们来继续学习. (博客地址:http://blog.csdn.net/developer_jiangqq),转载请注明. Author:hmjiangqq Email:[email protected] 为移动设备开发Web页面以及Web应用程序(Application)和开发传统的桌面Web浏览器相比存在一些不同的地方以及难点.为了帮助你更好的开发以及为移动设备开发出更加有效的Web应用程序,以下讲到的开发实践规则将会

Progressive Web Apps基本说明

我们将要做什么? 你将会学到 如何使用 "app shell" 的方法来设计和构建应用程序. 如何让你的应用程序能够离线工作. 如何存储数据以在离线时使用. 你需要 Chrome 52 或以上(浏览器建议使用正宗google Chrome,经测试chrome内核的百度浏览器无法安装下面的web server) Web Server for Chrome 或其他的网络服务器. 示例代码 代码编辑器 HTML,CSS 和 JavaScript 的基本知识 这份引导指南的重点是 Progre

[WebView学习之二]:使用Web Apps 支持不同分辨率屏

上一篇我们学习了(1.[WebView学习之中的一个]:Web Apps简单介绍),今天我们来继续学习. (博客地址:http://blog.csdn.net/developer_jiangqq),转载请注明. Author:hmjiangqq Email:[email protected] 由于Android设备有非常多分辨率,不一样的屏幕以及像素密度.所以在Web页面的设计过程中,应该考虑网页总能显示合适的大小. 在你为Android设备开发Web页面是,我们须要考虑下面两个因素: 1.:视

Building Web Apps with SignalR, Part 1

Building Web Apps with SignalR, Part 1 In the first installment of app-building with SignalR, learn how to build a real-time chat application. By Eric Vogel 01/22/2013 GET CODE DOWNLOAD More on this topic: Building a Chat Web App with Signal R, Part

[WebView其中一项研究]:Web Apps基本介绍

今天,我们开始了解WebView,以及Web Apps发展,从主要内容Android实际的例子来解释正式文件和后续. (博客地址:http://blog.csdn.net/developer_jiangqq),转载请注明. Author:hmjiangqq Email:[email protected] 基本上在Android上面我们有两个方式来提供应用程序(Application):1.基于client应用程序(还用AndroidSDK开发.在用户设备上面进行安装APK文件),2.基于web应

1.[WebView学习之一]:Web Apps简介

今天开始我们来学习一下WebView,以及Web Apps开发,主要内容来自Android官方文档以及后面的实战例子讲解. (博客地址:http://blog.csdn.net/developer_jiangqq),转载请注明. Author:hmjiangqq Email:[email protected] 基本上在Android上面我们有两个方式来提供应用程序(Application):1.基于客户端应用程序(还用AndroidSDK开发,在用户设备上面进行安装APK文件),2.基于web应

2.[WebView学习之二]:使用Web Apps 支持不同分辨率屏幕

上一篇我们学习了(1.[WebView学习之一]:Web Apps简介),今天我们来继续学习. (博客地址:http://blog.csdn.net/developer_jiangqq),转载请注明. Author:hmjiangqq Email:[email protected] 因为Android设备有很多分辨率,不一样的屏幕以及像素密度,所以在Web页面的设计过程中,应该考虑网页总能显示合适的大小. 在你为Android设备开发Web页面是,我们需要考虑以下两个因素: 1.:视图窗体(Th

May we can use Turbolinks or Pjax in our web apps

Turbolinks[1]: Turbolinks makes following links in your web application faster.Instead of letting the browser recompile the JavaScript and CSS between each page change,it keeps the current page instance alive and replaces only the body and the title