hotwire


Welcome to Hotwire, an umbrella for trio frameworks that implement the HTML-over-the-wire approach to building modern web application.
欢迎使用Hotwire,  它是三个框架的总称,它们实现了构建现代web应用程序的 HTML-over-the-wire 方法。

At its heart is Turbo, which gives you techniques for bringing the speed of a single-page application without writing a lick of JavaScript.
a lick of
核心是Turbo,给你技术让你快速构建单页应用,而不用写一点Javascript。
机器翻译:它的核心是Turbo,它为您提供了在不编写任何JavaScript的情况下提高单页应用程序速度的技术

This screencast will focus primarily on showing how Turbo works.
这段截屏视频主要集中在显示Turbo 如何运行的。

But most applications will eventually  need a few sprinkles of JavaScript to get the fidelity they need.
a sprinkles of 少量的 [ˈsprɪŋklz]
 fidelity   [fɪˈdeləti]  忠诚;忠实;(对丈夫、妻子或性伴侣的)忠贞;准确性;精确性
但大多数应用程序最终都需要少量的JavaScript才能获得所需的精准。

And for that purpose, we have Stimulus. It's a modest JavaScript framework for the HTML you already have.
为此,我们有Stimulus.js框架。一个适用于现有HTML的适度JavaScript框架。

I'll briefly show how this works to supplement the Turbo demonstration.
我将简单介绍Stimulus如何在Turbo的示例中如何进行补充

Finally, we have Strada, which provides bridge tooling for how the web and native parts of a mobile hybrid application built with Hotwire talk to each other.

We're still working on polishing Strada for release, so this screencast will not cover it.
[ˈpɑːlɪʃ]  磨光

Hotwire is what we've used to implement all of the front-end elements of hey.com, the new email service  by Basecamp.
hey.com是Basecamp的新电子邮件服务, 我们用Hotwire实现了 它的所有前端元素, 。

第二部分创建项目
>> rails new chat --skip-javascript
We're going to build a chat application using vanilla Rails plus (the Hotwire Rails integration gem), which includes the full setup for Turbo and Stimulus.
vanilla [vəˈnɪlə] 香草,普通的,
n. 香草;香草醛;香草香精(从热带植物香子兰豆中提取,用于冰激凌等甜食)
adj. 香草味的;有香子兰香味的;普通的;寻常的;毫无特色的

>> rails hotwire:install

The Hotwire install command will turn on Redis backing for handling WebSockets with Action Cable  at the import map needed for autoloading Stimulus controllers and a few other tweaks.
tweaks 英 [twiːks]   美 [twiːks]  v.扭;拧;扯;稍稍调整(机器、系统等)n.扭;拧;扯;(对机器、系统等的)轻微调整

The application will use two models, Room and Message.
One Room has many messages.
We'll use a full scaffold for the Room model to give us the basic editing interface, but only use the model generator for Message since it needs much less.

We're going to connect Room and Message together without using any of Hotwire to start.
 
This will give us a foundation flow for an admittedly cumbersome chat application which we can then use to level up with Hotwire techniques one at a time.
admittedly  [ədˈmɪtɪdli]  诚然;(尤用于句首)无可否认
cumbersome  [ˈkʌmbərsəm]  大而笨重的;累赘的;难以携带的;缓慢复杂的;冗长的;复杂的

For Messages , we'll have just two actions. New to render the form to create a message,  and Create to handle the form submission.

Note that we are also adding a partial template to encapsulate  rendering of the Message.

This partial is then rendered when  showing the Room relying on the conventional naming to tie them together.

This is all just standard, one-on-one rails stuff,  but let's start the server and try it out.

Here's our interface to create a new Room. Then we create a couple of new Messages for that Room.

Yes, technically a chat system, but not exactly a very dynamic or appealing one.
[əˈpiːlɪŋ]  吸引人的;有吸引力的;有感染力的;令人感兴趣的;恳求的;可怜的;希望同情的  v.呼吁;申诉;上诉;有吸引力;有感染力;引起兴趣;吁请;恳求  appeal的现在分词

So let's introduce our first Turbo feature, Frames.
第二部分 Frame

Turbo Frames  decompose pages into independent contexts, which can be lazy-loaded and scope interaction.

So when you follow a link or submit a form,  only the content of the Frame changes rather than the entire page.
打开一个链接或者提交一个表单,只有Frame里面的内容改变,而不是整页。

This allows you to keep the state of the rest of the page from changing, making the app feel more responsive.
这允许你保留页面的其他部分的状态, 让这个app感觉更灵敏。

To be able to easily see how the Frames work,  we'll call them out with a blue border.

Now let's wrap the Room name and the ability to edit it inside a Frame.

The Turbo Frame tag goes around both the initial display, including the Edit link, and the part of the Edit page  we want to appear within  the Frame.

We see our Frame wrapped in blue.

And when  clicking the Edit link, the form from the Edit screen is presented within.

And upon submission, it's replaced again with just a display.

If we go straight to the full page editing screen,  we can see it has both a header and navigation links,  parts we were emitting from the Frame.

Note that if we try to click a link within the Frame that goes somewhere without a matching Frame, nothing happens.
我们点击一个Frame里面链接,如果没有匹配一个Frame,那么什么都不会发生。。。

We can solve this by adding a Data Turbo Frame attribute that points to underscore top to break out of the Frame, (data_turbo_frame="_top") , just like traditional HTML frames.

Now the back link works and the Frame scopes the Edit Display loop.
现在,反向链接起作用,“帧”将作用于“编辑显示”循环。

Then lets add the New Message link into an inline but  lazy-loaded Turbo Frame tag that also, just for starters, acts on the whole page.

<%= link_to "New message" new_room_message_path(@room) %>
改成下面这样:

<%= turbo_frame_tag "new_message",  src:  new_room_message_path(@room),  target: "_top" %>

This Frame will be loaded right after the page displays,  hitting the New Message Controller action we made earlier.
Like with Edit, we wrap the relevant segment in a Frame tag with a matching ID, which is how Turbo knows how to plug out the right Frame.
像使用Edit一样,我们将相关片段包装在一个带有匹配ID的Frame标签中,这就是Turbo知道如何插入正确的Frame的原因。

You can now see two requests when we load the room-- one for the page, one for the lazy-loader frame.
Let's try to add a message.
It's works!
But this only demonstrates that the Frame was lazy-loaded. 但这只能说明框架是懒加载的。
Right now, we're resetting the whole page upon submission of the New Message form.

Whereas with the Room Name Frame, you can edit and submit without changing the rest of the page state, a real independent context.

You can see how the Frame replacement happens by inspecting the response to edit.

Rails knows when a request is coming from a Frame,so it won't render the layout, but that's just a nice-to-have have optimization.
[ˌɑːptɪməˈzeɪʃn]
n. 最佳(优)化;优选法;(使)最恰当(适宜,适合);最佳条件选择;求最佳参数

Turbo will plug out just the matching Frame,  regardless of whether the response is optimized or not.
Turbo将只插入匹配的帧,而不管响应是否优化。

As you can see here, the header and links are ignored.

Now let's turn to Turbo Streams.
They deliver page changes over WebSocket or in response to form submissions using just HTML and a set of CRUD like action tags.

The tags let you append or prepend to replace and remove any target Dom element from the existing  page.

They 're strictly limited to Dom changes, though. No direct JavaScript invocation.

If you need more than Dom change, connect a Stimulus controller.

We will add a Turbo stream response to the message creation action such that we can add the new Message to the Room page without replacing the whole page.

<%= turbo_stream.append "message", @message  %>
This template invokes the Append action with the Dom ID of the target container, and either a full set of partial rendering options or just a record we wish to render which conforms to the naming conventions for matching to a partial.
此模板使用目标容器的Dom ID调用Append操作,并使用一组完整的部分呈现选项,或者只调用我们希望呈现的记录,该记录符合与部分匹配的命名约定。

Now we can add Message to the page without resetting it completely.

The Edit Name form can stay open while we're doing this, because new Message are added directly to the Messages div.

The Turbo Stream HTML is rendered directly in response to the form submission, and Turbo knows from the MIMIE type to process it automatically.

But notice the input field isn't cleared.

We can fix that by adding a Stimulus controller.

The new Rails Stimulus integration gem ships with an autoloader for you controllers.

This is done with an import map supported by es-moudle-shim,  and the unprocessed ES6 controller code that's loaded by the browser directly via ESM.

You can of course continue to use Stimulus with your existing  JavaScript bundler and transpiler, but this give you a taste of how far we're able to get with native browser controls now.
#reset_form_controller.js

reset() {
  this.element.reset()
}

<% form_with(model: []..., data:{controller: "reset_form", action: "turbo:submit-end->reset_form#reset" }) do |form| %>

The Stimulus controller we're going to add will be a dead simple way to reset the form after creating a new Message.

It has just one method, Reset, which we will call when Turbo is done submitting the form via Fetch .
它只有一个method: Reset, 在Turbo完成通过Fetch的提交的时候调用。

Reload to pick up the new Stimulus controller.
Then let's add another Message.
And voila!  那就是,瞧(表示事情成功或满意之感叹词用语)
The form is reset and the Message added dynamically.

But how interesting is a chat app where you're just talk to yourself?

Let's start a conversation with another window.
You'll see that new Messages are only added live to the originator's window.
[əˈrɪdʒɪneɪtər]  发起人

On the other side, we have to reload to see what's been said.
Let's fix that.

The first thing we'll do is establish  a WebSocket connection to the stream identified by the Room we're in.
This is done with a Turbo Stream From tag using a tamper-safe signed identifier in the view.

You can see the connection has been made to the Turbo Stream's  channel running over action cable from the inclusion of this text.

Let's send new Messages to this Stream by adding a broadcast call to the Message creation.

This method call mirrors what we're already doing in the Turbo Stream template, just over WebSocket now.
这个方法调用反映了我们在Turbo Stream模板中已经在做的事情,现在就在WebSocket上.

Now we can add a new message and see it appear in both windows.

But you'll see the originator sees double, because the Turbo Stream response from the form submission is still in place.
发起人看到了2次,因为TurboStream响应的也还在那。

On the other side, we can inspect the traffic on the action cable WebSocket and see that the very same message partial is being used to render the update over there, wrapped with the same Turbo Stream tag .

There, we don't have a form submission,  so the message is only added once.

Let's fix the double vision by removing the Turbo Stream returned in response to the form submission.

For now, we'll just leave a note to document the dead end this will be without a cable connection.

And now all updates are sent only once.

We can also Turbo Stream deleting messages.
We'll add a similar model call back and destroy the triggers that remove broadcasts sent to the same Stream.

But instead of adding in a UI, let's try to invoke this flow from the console.

Here, you can see the destroy carried out,  didn't spot the broadcast sent using the Turbo Stream Remove Action tag.
在这里,您可以看到执行的销毁,没有发现使用Turbo Stream Remove Action标签发送的广播。

I call it again.
Another line disappears from both browser windows.

You can also create a new message straight from the console.
And here, you'll see the same Turbo Stream Append action with the same partial template,  as with all the other examples.

Finally, we can add a replace to happen with the message is updated.
This follows the same flow and completes the life cycle of callbacks you'd normally respond to with Stream updates.

Let's invoke from the console.
Whoops!
We have to reload our console instance to pick up the code change.

And here we go.

The last message in the chat has been updated.
But it can be even simpler.
If you want a full menu of basic lifecycle updates,  you can replace these three callbacks with a single broadcast to declaration.

broadcasts_to :room
等于==>
after_create_commit -> {broadcast_append_to room}
after_destroy_commit -> {broadcast_remove_to room}
after_update_commit -> {broadcast_replace_to room}

You'll notice that this setup even uses async broadcasts.
Surrendering is done out of band by a job queue.
通过Job队列完成的

Let's add this shortened form directly to changing the room as well.
Since the room identifies the Stream, we could just use broadcasts.

Then we'll extract the room display as its own partial so it'll match the conventions assumed by the callback,  render it in line, and give it a DOM ID.

Now you can edit the name in one window and instantly see it updated in the other.

So that's Hotwire, an alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire.

We get to keep all our template rendering on the server, which means writing more of our application in our favorite programming languages.

Now let's have a quick look at how these techniques are used  in a real-life application.

We at Basecamp launched our new email service, hey.com,  this past summer.
And with 40 kilobytes of compressed JavaScript, we're able to deliver a full featured modern and successful email app, complete with  native applications across all the major platforms, powered by the same majestic monolith running on Hotwire.
通过40 KB的压缩JavaScript,我们能够提供一个功能齐全的现代成功的电子邮件应用程序,包括所有主要平台上的本地应用程序,由运行在Hotwire上的同一个宏伟的整体提供支持。

HEY uses lazy-loaded Turbo Frames to fetch the Reply Later and Set Aside trays at the bottom of the inbox.
These trays change far less frequently than the inbox itself, so they're excellent candidates to run on a different caching schedule.
收件箱和底下的trays托盘,托盘不怎么改变。

They're loaded as soon as the initial page is loaded, but will 302 when already cached when you hit them a second time.
初始化页面后就会加载,或者再次通过缓存加载。 ()

We also use lazy-loaded Turbo Frames for menus, but these are not loaded until you access them for the first time.
懒加载菜单(HEY, ME), 第一次加载后才会被加载

上面有三部分内容了: 1,待回复的收件,2,固定在底部的trays,3,HEY菜单, 4.ME 菜单。

Once loaded, you'll be hitting that browser cache on subsequent visits。
This technique is used for both the HEY menu and the ME menu,  both triggered by the detailed summary tag pairing .
此技术用于HEY菜单和ME菜单,两者都由详细的摘要标记配对触发

The Topic page uses even more lazy-loaded Turbo Frames, since the basic page is shared amongst all users on a company account for maximum cache efficiency and all per-user specialization is therefore done with Frames.
因为基本页面在公司账户上的所有用户之间共享,以实现最大的缓存效率,因此每个用户都可以使用帧进行专门化。
But let's focus just on the Toolbar Frame, which is where you start a new reply.

When Reply Now is pressed, that Frame is replaced with a Frame response of the New Message page.
If we delete that response, we again replace the Frame with the toolbar.

Barely any JavaScript needed for the whole interaction.
整个交互几乎不需要任何JavaScript。

Now let's go back and have a look at the Screener.
The Screener actions are synchronized between browser windows, just like in our chat demo application.

Although here, updates needed for different pages require unique responses.

So we simply remove the pending email from the Screener upon clicking the button, but we update both the Screener button and reveal the Screened in email in the inbox on the other screen.
因此,我们只需在单击该按钮时从筛选程序中删除待处理的电子邮件,但我们会更新筛选程序按钮并在另一个屏幕的收件箱中显示已筛选的电子邮件。
reveal  [rɪˈviːl]  揭示;显示;透露;显出;露出;展示  n. 启示;窗侧壁;门侧;(汽车的)窗框, 记忆技巧:re 相反;反对;不 + veal〔= veil〕盖上 → 不盖上 → 揭露

You'll see we're still using a slightly older Turbo Stream syntax here, with a template tag as the head,  but we'll be changing to the latest API shortly.

Lastly, you'll see how Frames and Streams can interact.

When you set aside an email, we remove it from the inbox,  then add it to the Set Aside tray in both windows at once.

The removal in the Acting window is done via Turbo Stream response to the action, and then both windows have the Set Aside email added to the tray over WebSocket.

You can check out all the ways we've used Hotwire to build HEY by signing up for free trial account.

All the JavaScript is available with source maps.

All the Turbo interactions were sent in the clear,  so there's plenty to learn from, and we welcome you to borrow or steal our Stimulus controllers.

I hope you've enjoyed this quick tour of Hotwire, and Turbo in particular.

Please help us further develop all the frameworks as open source.
阅读量: 733
发布于:
修改于: