 Sam Stevenson, you might know me from some of my open source contributions. I work on a lot of projects, RBN, Pow, and Tricks most recently. I was one of the original Rails core members back in the day, though I think nearly all of my code has been deleted or replaced by now. I hope. I work for Basecamp. And last year, we launched an all-new version of Basecamp called Basecamp 3. We created it in 18 months from the initial concept through dozens of designs and iterations and changes. And it's a big app, has over 200 screens. And we shipped it in 18 months on five different platforms simultaneously. So desktop web browser, mobile web browser, an Android native client, an iOS native client, and email. And we did all this with a tiny team. At Basecamp, we have six Rails developers, two Android developers, and two iOS developers. And we also built three open source frameworks during that time period. Tricks, which is our rich text editor. Action cable, which I'm sure you're familiar with, in Rails 5. And also TurboLynx 5. And this all sounds kind of unbelievable when I say it, that we managed to do all of this. But really, TurboLynx 5 was a secret weapon that led us to it. And it's an entirely new version of TurboLynx. And it's one of the banner features of Rails 5, and it's what I'm here to talk about today. So how many of you are familiar with TurboLynx? Anyone? Right. It's that thing you have to disable every time you start in Rails 5. But seriously, it's that thing that breaks all your GQuery plugins. TurboLynx is misunderstood. And to understand it, you need some context. And that's what I hope to give you today. Because I think it represents a philosophy of web development that deserves our attention. I've been at Basecamp for 10 and a half years now, which is a small eternity in tech years. And being at the same place for a decade has been really interesting. I've had the opportunity to go deep on a lot of interesting problems and see the effects play out over many years. Every once in a while, I think it can help to take a step back and look at where you're going and where you've been. And things were really different when I started. Do you guys remember what web development was like before Rails? The only way to make real software was with J2EE. I found this diagram of 2004-era best practices. This is J2EE. It's beautiful in its simplicity, really. And then, of course, on the other end of the spectrum, you had PHP. But Rails came along and changed all of this. For the J2EE developers, it threw out all of that ceremony and it said, hey, look, this can be a lot simpler. And for the PHP folks, it said, here's some structure that makes sense, and it helps you avoid writing the same thing over and over again in slightly different ways. And I think in some ways, this period was the golden age of web development. If you ignore the browser situation at the time. Because we could embrace the statelessness of HTTP. And we could deliver a fully rendered UI on each request and response cycle. So this is the full request response cycle. I think it's one of the best features in Rails. And then web apps got more ambitious. We weren't happy with the performance of the UI built on full page loads. So we started building single page apps. And instead of each request returning a full HTML response, now we started booting the app with an empty page. And the page had some JavaScript. JavaScript made JSON API requests. So we made the server side accept and returned JSON. And we moved the traditional responsibilities of rendering the UI completely into the client. And everybody agrees this is a good idea, right? We've got Rails 5 with Rails API. And you need an API anyway in your application. So why not consume it yourself? Plus, we want to decouple presentation from data. And decoupling is good, right? But eventually we weren't happy with the performance of client-side NBC. So we introduced a virtual DOM to minimize the number of DOM operations we make, which is a great idea. The DOM is slow. So we should make as few changes to the DOM as possible. But you know what? All that JavaScript and all those API requests really make the initial page load take a long time. And all the content on the page is inaccessible to search engines. So we decided to get rid of the boot page and render HTML instead. Which means now we need a JavaScript runtime on the server. We need to make our client-side code run on both sides. And we need the virtual DOM there too, because we don't have a real DOM. And now we've come full circle and we've reinvented what we had in 2004. But the complexity has increased exponentially. And we've introduced literally hundreds of new dependencies to our applications. And the whole system is now too much for any single person to reasonably keep in their head at once. You need entire teams dedicated to managing these subsystems, which honestly is not a surprise. How many of you are familiar with Conway's Law? Conway's Law says that the structure of the software system necessarily mirrors the structure of the organization that created it. And these client-side patterns and frameworks that we're using now they came from Google and Facebook, which are huge companies with thousands of developers across dozens of teams. Well, guess what? Your team probably doesn't have the same problems as Google and Facebook. So how did we get to this point? Well, we were chasing performance. And each one of these changes that I went over makes sense incrementally. Each one made sense at the time. But looking back, we can see the compound effects. We traded small performance benefits for a massive regression in developer ergonomics. And the further we go down the rabbit hole, the more we constrain our future choices. And in 2016, this only tells part of the picture, because you need native apps on Android and iOS too. So you can multiply the complexity on that slide by three. And you'll be re-implementing each one of your app screens natively, of course, because you want the best performance. So you're going to have to multiply your head count by three too. I'll fuck that. But if we accept that we need performance web and mobile apps, how do we reconcile this complexity with the Rails philosophy, which says we want to empower small teams to do big things? Well, what if I told you it didn't have to be so complex? That we could return to the golden era of the request and full-response model to rendering HTML views from Rails without an intermediate API layer, and that we could use those same HTML views as a baseline for your iOS and Android apps in a way that still feels native? This is what TurboLynx 5 gives you. It turns your traditional web application into a single-page app. At a high level, it intercepts link clicks on your page, and it turns them into Ajax requests. Your server renders a full HTML response, and TurboLynx loads that response, pulls out the head, merges it in with the current page's head, pulls out the body, and outright replaces it. And from the server's perspective, nothing changes. But the client adopts the single-process model, which means that it doesn't incur the expense of reloading and reprocessing assets on each page change or re-establishing WebSocket connections, which are very expensive. And this gives you a significant performance boost. So how significant? This is a side-by-side comparison we recorded last night on the hotel Wi-Fi. There's TurboLynx on the left and a full-page load on the right. Oops, I'm sorry. Did you guys see it? This is what we call good-enough performance. If you can transition from a click to the next screen in about 300 milliseconds, you're going to perceive it as instantaneous. Anything longer feels slow, but anything shorter is nearly imperceptible for full-page changes. But chasing that kind of performance requires an inordinate amount of effort and resources. So here's my highly scientific graph. But I think it's mostly accurate, right? We know from experience that it takes a lot of work to get on the left side of that graph. But 300 milliseconds, actually getting your Rails request to complete in, say, 100 milliseconds is totally achievable. And there's a wide array of tools and strategies to help you get there, like Russian doll caching, e-tags, lazy loading, content after the page loads. And all of that's for a different talk. But the point I want to make here is, in most cases, getting faster than 300 milliseconds just doesn't matter. And what you gain with turbo-links approach is a dramatically simpler model. If you understand this line of code, you understand the core turbo-links concept. It's understandable because it's just HTML. HTML is easy to generate from Rails. It's easy to cache. The designers already know how to work with it. They can work directly with your views instead of making Photoshop comps or instead of waiting on an API team to expose the data that they need. And when you combine this approach with responsive design techniques, your design team can create a single set of views that power desktop web, mobile web, and hybrid native apps. And this is incredibly powerful. And with turbo-links 5, we made it incredibly easy. We've created these iOS and Android frameworks that let you build native hybrid apps around our turbo-links web app. And they're designed for what we call the hybrid sweet spot, which means that in your native app, your view content is mostly HTML, but the surrounding navigation controls are native. And when you want to go fully native, you can do so on a per URL basis. You get to decide. And you get all the performance benefits of turbo-links in your hybrid app. But it's even more efficient because in a native app, we use a single shared web view that we automatically move around from screen to screen. So not only do you avoid reloading assets, re-establishing web socket connections, and so on on every page change, you also use less memory. And by the way, a native animation transition from one view controller to another is about 350 milliseconds. So I'd like to show you how easy it is to build an iOS app using turbo-links. So I'm going to do it live. But actually, it's not really live. I recorded videos. But it's not edited. So this is Base Camp 3. It's running on my development machine. So it's not as fast as it would be in production. But I'm just going to click around for a bit. So you can see turbo-links in action here. And as I mentioned before, we have fully responsive views in Base Camp 3. So when I resize the window, you'll see what eventually becomes a phone view. And this lets us share all of our views across all of our platforms. All right, so now let's open Xcode. And we'll create a new project. Sorry, I need some music. We're going to take everything out of this file. We're going to start with a totally bare bones iOS app. The only thing we'll have in here is the application did finish launching called back. All right. So we're going to set it up as a UI navigation controller app. For those of you who aren't familiar with building iOS apps, a UI navigation controller is basically the stack of views that you are probably familiar with from any iOS app. So I'm going to create one and assign it to the Windows root view controller. And I'm going to build and run the app just to make sure that it works. It's not going to look like much of anything. There it is. All right. So I need to change the setting now. And that's because iOS 9 ships with a feature called application transport security. And this is a setting that's on by default. And it prevents you from accessing any non-https websites, basically. So for development, we're going to flip that off. So I'll just run this command and do that. And obviously you don't want that in production. And now I'm going to go and just drop the TurboLynx framework in the app. And in a real app, you're probably going to want to use Cocopods or Carthage or some other package manager to get TurboLynx in there. But for now, this is the simplest way. And finally, I'll import the TurboLynx framework. We'll compile and run. It still works. We're going to point TurboLynx to our web application. And so to do that, we're going to create a session. And the session comes from the TurboLynx framework, and it orchestrates moving that web view around from controller to controller. We're going to define a function called visit that accepts a URL. And what this is going to do is create a view controller. It's going to push it onto the navigation controller stack. And it's going to tell TurboLynx to visit, which means load the web page. And now we're going to need to make an initial visit when we start the app. It's got to load something to begin with. So I'm going to grab the Basecamp Development URL and we'll visit that. Moment of truth. There we go. So you can see we have native controls around the web view. But now tapping on a link doesn't do anything. And that's because we need to handle visit proposal. So in order to do that, we need to set ourselves as the TurboLynx session delegate. And then we need to implement a method. And this method just tells the application what to do when TurboLynx receives a link click event. So we're going to implement that method now. Session did propose a visit to URL. And we're just going to tell it to call our visit method that we already wrote. And we're going to compile and run. But we forgot to implement our error handling method. This is very important to implement. But I'm just going to stub it out for now. And let's try tapping a link. Oh, there it is. And so we had a native transition. We have two actual native view controllers. It looks and behaves like a real iOS app. But the content of the view controller is rendered as HTML. And you can see it's very fast navigating backwards. You can even perform the interactive pop gesture here. So you're seeing the web view on front and the screenshot in the back. We manage all of that for you. You get pulled to refresh. But you can see that this doesn't quite look like we really wanted to, right? We've got all that administrative debris left over from the web app. But we want native navigation. And so what we do at Basecamp is we check for a custom user agent in the web app. And we use that to selectively disable some UI elements. And it's very easy to do. So I'm just going to go and import the WebKit framework here and make a change to the way I create this session. I'm going to create a configuration object. Assign this property. Pass it to the session initializer. And now we'll run the app. And now you can see it's looking more native. We've lost the navigation bar at the top. Some buttons. And now when I navigate through, some of these views look like they could be real table view controllers. But of course we're doing this with much less effort. And so this is the core of our strategy, right? I just built a fully functioning iOS app wrapper for Basecamp in under 10 minutes. It has native navigation. And I think the most impressive part of this is that I have full coverage of the app. All 200 plus screens are accessible from this wrapper in 30 lines of code. And when we roll out a new feature in the web app, in many cases we don't have to make any changes to the native apps. And we don't need to wait on app store approval. Because when we make a hybrid app with turbo links, we're essentially making a custom browser. We get baseline coverage for all the apps functionality. And we can progressively enhance the native controls surrounding the web view. This is how the web is built, progressive enhancement. Now we're extending that from the browser to native apps. And what that means is we can spend our time building high-fidelity UI where it's most valuable instead of going through the grunt work of recreating each screen natively on every platform. We can even go fully native on a per-screen basis. And this is what we do in the official Basecamp iOS app. It has a native home screen. So I'll show you here. Instead of taking you directly to the activity page, we have a native jumping off point. And this is just using API requests to populate the table view. But as soon as you click something, then you're taken into a turbo links web view. Everything proceeds from there. Also, we have native navigation controls in the title bar. We extract some metadata from the page in order to generate this title. We have custom navigation buttons. So all of this is native. But the actual application is in HTML. Very fast. We even support 3D touch using turbo links. So yes, you can do all those things that you expect. We have native menus. And again, we extract this content from metadata on the page. It's very easy to do. So that's a look at what TurboLinks 5 gives you. New Rails 5 projects have it enabled by default. It's also now available on NPM, as a standalone JavaScript library. So if you're not using the Asset Pipeline and you want to use it, it's available to you there. And it has no dependencies. And it's not just for Rails apps anymore. It's open to everyone. We have all new documentation on the GitHub project page, which we're very proud of. The iOS and Android adapters are exhaustively documented as well. And so if any of this speaks to you, I encourage you to check it out. And there's one final note I want to make before I go. It's important to note that TurboLinks is not about avoiding having to write any JavaScript, okay? Your application will need JavaScript. Nor is it about having to avoid learning native development. Any more than ActiveRecord is about avoiding how to write SQL. You're still going to have to know how to do these things. But what TurboLinks does is it gives you a baseline for good performance with minimal effort, which lets you spend your time choosing what to optimize instead of going all in and recreating everything from scratch. So that's, if you take home one point from this talk, that's what I hope it is. Choose what you optimize. Thank you for coming. My question is, why is it still called TurboLinks if we rewrote it? So we decided to call it TurboLinks 5 because the core idea is still the same, but it's a pretty massive shift, and we wanted to sync the version numbers up with Rails 5. I believe your question is, is this compatible with jQuery UJS techniques? Yes, it is. This only intercepts link clicks that would otherwise fall through to the browser as standard navigation. The question was, does this support offline applications? And TurboLinks does not have support for offline, but this should be possible to build with service workers on top of TurboLinks. The question was, could you do this in RubyMotion? And yes, you could. You could probably also do it in React Native. Because the TurboLinks WebView is just a standard Cocoa component, so you can orchestrate it with anything that can talk Cocoa. The question was, does the WebView wrap WK WebView? Yes, it does. Inspectable, you can go in through Safari and inspect the Web application as you're developing it, whether you have a phone connected to your computer or whether you're running in the simulator. And you get all the performance benefits of WK WebView, so you get the full JIT JavaScript engine. The question is, do you still need to write jQuery load event handlers? And you need to re-architect the way you initialize JavaScript on the page. So we provide a series of events. There's a TurboLinks load event that fires on every page change. And then in our documentation, we have some further recommendations for how to approach that. But what I really recommend is using Mutation Observer or Custom Elements. These are HTML5 APIs. And they give you callbacks for when elements are attached and detached from the DOM. And if you use these APIs to install and remove JavaScript behavior, it will work transparently whether you're using TurboLinks or not. And I think it's just a much better approach. The question is, is there an API for rendering just a section of the page rather than the whole page? And there was a version of TurboLinks called Version 3, which is in development for about a year and a half and never officially shipped. And it had a feature called partial page updates. And it expanded the TurboLinks API in a way that didn't seem proportional to the amount of value it provided. And I also just think partial page updates in many ways are orthogonal to navigation. So my recommendation for now is to use an SJR technique. So just like a JS.ERB response. And combined with a UJS remote link to update parts of the pages as necessary as well as for handling form submissions and stuff like that. But in the future we might explore adding partial page updates API. Yeah. The question was, if I can summarize, if you're rendering hundreds of partials in a single response, does TurboLinks handle that well? And the answer is it's going to handle it as well as the browser would, really, if you were sending that over a full response. But my suggestion would be, one, don't return so many results at a time. Use infinite pagination or other techniques like that to load continent on demand. And two, it would be to consider writing that in client-side MVC. It's totally fine to do that. And we do that in a lot of places. I think the difference is doing that in a component-based way versus saying you're going to write your entire application that way, right? It's much easier to optimize something by just looking at it as a component to throw everything out and write the whole thing twice on both sides. The question was, what's the upgrade path like from older versions of TurboLinks? And we don't have a good guide for that right now. All of the event names have changed and there are some events that went away. But we'll be documenting that before the final Rails 5 release. Yes? Any plans to support Windows Phone? I don't have any plans, but if someone wants to look into that, you can take a look at the way we built the iOS and Android adapters. So there's a JavaScript component that's bundled with each one that basically communicates with the WebView over a bridge. So you could take the same approach on other platforms. We'd love to see that happen. The question was, can you use TurboLinks to access GPS, camera, and so on? And the answer is yes, of course, because you're still writing a native app, okay? If you want to get that over the WebView, you'll have to use the bridge that's provided by the operating system. But it's really orthogonal to TurboLinks. You would build the native UI yourself, and then you would use the platform-specific WebView bridge to get that into your application if you wanted, or you could consider using, for example, a JSON API to communicate with your application if it's simpler. All of those things are possible. The question was if you want to access SQLite, how would you do that from a web page? Local SQLite. You would do that over the JavaScript bridge that's provided by the operating system. So in that way, it's not any different than using a normal WebView. The question was how do the native adapters handle request failures? And all request failures go through a single method handler, so you choose how to implement that in your application. My recommendation is to show a native view that gives you basically a reload button or retry button. That's what we do in Basecamp. All right, thank you so much for coming.