Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

터보링크(Turbolinks)에서 터보(Turbo)로 마이그레이션하기

웹 애플리케이션의 페이지 이동 속도를 크게 높여 주던 훌륭한 도구인 터보링크(Turbolinks)는 더 이상 활발하게 개발되지 않습니다. 그 자리를 이어받은 것은 Hotwire 계열에 속하는 새로운 프레임워크, 바로 터보(Turbo)입니다.

개발팀은 터보링크에서 도출한 개념들이 프레임(frames), 폼 제출(form submissions), 네이티브 앱 같은 다른 영역에도 적용될 수 있음을 인식했고, 이를 바탕으로 더 빠른 웹을 지향하는 확장된 프레임워크를 만들어냈습니다.

이 글에서는 터보링크의 새로운 대체제인 터보 프레임워크를 소개하고, 터보링크에서 가장 중요하고 널리 사용되던 기능들을 터보에서 어떻게 활용할 수 있는지 실전 마이그레이션 가이드와 함께 설명합니다.

본문 내용을 원활하게 이해하려면 터보링크에 대한 기본적인 지식이 필요합니다. 관련 내용은 공식 문서를 참고하시기 바랍니다.

터보(Turbo) 소개

터보는 적은 양의 JavaScript만으로 현대적이고 빠른 웹 앱을 만들 수 있게 해주는 여러 기술의 모음입니다. 서버가 모든 로직을 처리하고 그 결과를 HTML 형태로 브라우저에 직접 전달하며, 브라우저는 일반 HTML을 처리하는 역할만 담당합니다.

이를 위해 터보는 다음과 같은 네 가지 핵심 구성 요소로 나뉩니다:

  • Turbo Drive: 터보의 심장부입니다. 링크와 폼에 대한 모든 클릭을 자동으로 가로채 브라우저의 전체 페이지 리로드를 방지하고, History API로 URL을 변경한 뒤, 백그라운드에서 Ajax로 페이지를 요청하여 응답을 적절하게 렌더링합니다.
  • Turbo Frames: 페이지의 일부 영역(프레임)을 독립적으로 다루던 기존 프론트엔드 개발 방식을 새롭게 재정의합니다. 페이지를 별도의 스코프와 지연 로딩(lazy load) 기능을 갖춘 독립적인 컨텍스트 집합으로 분해할 수 있습니다.
  • Turbo Streams: 간단한 CRUD 컨테이너 태그 세트를 통해 웹 소켓 기반의 비동기 전송으로 부분 페이지 업데이트를 대체합니다. 동일한 웹 소켓으로 HTML 조각을 전송하면 페이지가 이를 이해하고 UI를 다시 처리합니다.
  • Turbo Native: iOS/Android 네이티브 셸에 웹 뷰를 임베딩하는 경우, 네이티브 앱에서 터보를 활용하는 데 필요한 모든 도구를 제공합니다.

프로젝트 준비

작업 속도를 높이기 위해 Bootstrap 스타일이 적용되고 게시물(posts) 폼 흐름이 스캐폴딩된, 바로 사용할 수 있는 Rails 앱 프로젝트를 제공합니다.

덕분에 환경 설정에 시간을 낭비하지 않아도 되고, 터보링크가 자동으로 포함된 작동하는 프로젝트를 바로 받을 수 있습니다. 물론 이미 진행 중인 프로젝트가 있다면 그 프로젝트로도 이 글을 충분히 따라갈 수 있습니다.

rails 명령어로 새로운 스캐폴드 Rails 앱을 생성할 수도 있습니다.

예제 저장소의 GitHub 링크를 통해 저장소를 로컬에 클론한 뒤, 다음 명령으로 모든 Rails 의존성을 설치하세요.

bundle install

설정이 완료되면 rails s 명령으로 Rails 서버를 시작하고 /posts 경로에 접속해 확인합니다.

터보링크(Turbolinks)에서 터보(Turbo)로 마이그레이션하기 Rails에서의 Posts CRUD 화면

터보링크의 기능이 실제로 어떻게 동작하는지 확인하려면, 새 게시물을 생성하거나 항목을 조회하는 링크를 클릭해 보세요. 페이지가 리로드되지 않은 채 URL만 변경되는 것을 확인할 수 있습니다.

마이그레이션 절차

먼저 Node 패키지 설치부터 시작합니다. 더 이상 turbolinks가 필요하지 않으므로 Node 의존성 목록에서 제거하고, 대신 turbo-rails를 추가하면 됩니다. 아래 두 명령어를 순서대로 실행하세요.

yarn remove turbolinks
yarn add @hotwired/turbo-rails

Rails 프로젝트라면 아래 명령으로 모든 것이 올바르게 설치되었는지 확인할 수도 있습니다.

rails turbo:install

이 명령은 Webpacker가 설치된 애플리케이션(이 예제에서는 설치되어 있습니다)에서 npm을 통해 터보를 설치합니다. 또한 누락된 것이 있을 경우를 대비해 프로젝트에 남아 있는 터보링크의 오래된 의존성 제거도 함께 시도합니다.

다음으로 app/javascript/packs/application.js 파일을 열어 아래 코드를 찾습니다.

import Turbolinks from "turbolinks";

Turbolinks.start();

Rails 버전에 따라 import 구문이 약간 다를 수 있습니다(구버전에서는 import 대신 require를 사용). 하지만 처리 과정은 동일합니다.

이 코드를 아래 한 줄로 교체합니다.

import "@hotwired/turbo-rails";

네, 딱 한 줄의 import면 충분합니다. 별도로 무언가를 시작(start)할 필요가 없습니다. import 시점에 Turbo 인스턴스가 자동으로 window.Turbo 객체에 할당되므로 관리가 훨씬 편리합니다.

터보링크가 아닌 터보만 로드되는지 확인하기 위해 파일 끝에 아래 코드를 추가해 보겠습니다.

$(document).on("turbolinks:load", () => {
  console.log("turbolinks!");
});
$(document).on("turbo:load", () => {
  console.log("turbo!");
});

브라우저에서 페이지를 리로드한 뒤 콘솔 로그에 무엇이 출력되는지 확인합니다.

터보링크(Turbolinks)에서 터보(Turbo)로 마이그레이션하기 터보 load 이벤트 로그 확인

추가로 수정해야 할 부분이 하나 더 있습니다. app/views/layouts/application.html.erb 파일에서 기존 터보링크의 data 속성을 터보의 대응 속성으로 변경하는 것입니다. data-turbolinks-* 속성을 사용하는 두 태그를 찾아 아래와 같이 교체하세요.

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbo-track': 'reload' %>

이 설정은 터보가 head 태그 안의 모든 임포트된 에셋을 자동으로 추적하고, 해당 에셋이 변경될 경우 리로드하도록 하는 데 중요합니다. 스크립트와 스타일시트를 포함한 모든 파일에 적용됩니다.

터보 주요 기능 살펴보기

터보는 애플리케이션의 모든 링크를 자동으로 가로채 리로드 없이 호출하기 때문에, 특정 링크에 대해서만 이 기능을 비활성화하려면 명시적으로 코드를 작성해야 합니다.

app/views/posts/index.html.erb 파일의 "New Post" 버튼을 아래처럼 수정해서 테스트해 보겠습니다.

<%= link_to 'New Post', new_post_path, :class => "btn btn-primary btn-sm", "data-turbo" => "false" %>

새로운 data 속성인 data-turbo를 추가하여, 이 특정 링크가 클릭될 때 터보가 개입하지 않도록 요청하는 것입니다.

앱을 리로드하고 "New Post" 버튼을 클릭하면, 이제 일반 앱처럼 페이지 전체가 리로드되는 것을 볼 수 있습니다.

이는 터보가 애플리케이션에 정상적으로 설정되고 동작하는지 확인하는 좋은 방법이기도 합니다.

폼(form)도 마찬가지입니다. 터보는 모든 폼 제출을 자동으로 처리하여 비동기 방식으로 동작하게 합니다. app/views/posts/_form.html.erb 파일의 폼에서 이 기능을 비활성화하려면 submit 버튼을 아래처럼 변경하세요.

<div class="actions">
    <%= form.submit class: "btn btn-primary btn-sm", "data-turbo" => false %>
</div>

앱을 리로드하고 테스트해 보세요. 새 게시물을 생성할 때 동일한 동작(전체 리로드)이 나타나는 것을 확인할 수 있습니다.

폼 제출(Form Submissions)

폼에 관해 좀 더 이야기해 보겠습니다. 터보는 폼을 링크와 매우 유사한 방식으로 처리하지만, 폼 요청이 항상 성공적으로 끝나는 것은 아닙니다.

실제로 확인해 보겠습니다. 먼저 게시물의 nametitle 속성을 필수 값으로 만드는 유효성 검사를 추가합니다. app/models/post.rb 파일을 열어 아래와 같이 변경하세요.

class Post < ApplicationRecord
    validates :name, presence: true
    validates :title, presence: true
end

앱을 리로드한 뒤 모든 필드를 비운 채 새 게시물을 추가해 보세요. 아무 일도 일어나지 않는다는 것을 알 수 있습니다. 브라우저 콘솔을 열어보면 다음과 같은 메시지가 출력됩니다.

터보링크(Turbolinks)에서 터보(Turbo)로 마이그레이션하기 Form responses must redirect to another location 오류

이 문제를 해결하는 방법은 두 가지가 있습니다. 첫 번째는 게시물 컨트롤러의 업데이트 관련 액션(POST, PUT 등)에 status를 추가하고, 처리 불가능한 엔티티(unprocessable entity) 객체를 값으로 전달하는 것입니다.

post_controller.rbcreateupdate 메서드에 대한 코드 변경 내용은 아래와 같습니다.

# def create
format.html { render :new, status: :unprocessable_entity }

# def update
format.html { render :edit, status: :unprocessable_entity }

변경 사항을 저장하고 폼을 다시 테스트해 보세요. 이번에는 유효성 검사 오류가 정상적으로 표시됩니다.

터보링크(Turbolinks)에서 터보(Turbo)로 마이그레이션하기 UI에 유효성 검사 오류 표시

두 번째 방법은 turbo_frame_tag를 사용하는 것입니다. 터보를 사용하는 Rails 앱에서 앞서 언급한 터보 프레임은 이 태그를 통해 렌더링됩니다.

페이지의 특정 부분을 격리하고 백엔드 앱과 직접적인 통로를 열어, 터보가 해당 프레임에 요청과 응답을 연결하도록 하고 싶을 때 매우 유용한 기능입니다.

테스트하려면 먼저 _form.html.erb 파일의 전체 내용을 이 태그로 감싸야 합니다.

<%= turbo_frame_tag post do %>
    ...
<% end %>

폼의 경우 당연한 이유로 post 객체가 사용됩니다. 브라우저로 돌아가 다시 테스트하면, 예상대로 동일한 유효성 검사 오류가 표시됩니다.

여기서 또 하나 주목할 점은 이 폼에 대해 생성된 HTML입니다. 살펴보겠습니다.

<turbo-frame id="new_post">
  <form action="/posts" accept-charset="UTF-8" method="post">
    ...
  </form>
</turbo-frame>

터보는 바로 이 커스텀 HTML 엘리먼트를 통해 프레임 기반 동작과 전체 페이지 기반 동작을 구분합니다.

프로그레스 바(Progress Bar)

브라우저의 기본 로딩 메커니즘을 제거했다면, 페이지가 느리게 로드되는 경우를 대비해 대체 수단을 제공해야 한다는 것은 상식입니다.

다행히 터보는 Bootstrap이나 Material Design 같은 주요 라이브러리가 제공하는 것과 매우 유사한, CSS 기반의 프로그레스 바를 페이지 상단에 기본으로 내장하고 있습니다.

이 프로그레스 바는 요청 처리에 500ms 이상 걸릴 때만 표시되도록 설정되어 있는데, 테스트 프로젝트에는 그다지 긴 시간이 아닙니다.

스타일을 변경하거나 완전히 제거하고 싶다면 .turbo-progress-bar CSS 클래스를 조정하면 됩니다. 예시는 아래와 같습니다.

.turbo-progress-bar {
  height: 15px;
  background-color: gold;
}

테스트를 위해서는 application.js 파일에서 프로그레스 바의 지연 시간을 아래 코드로 줄여야 합니다.

window.Turbo.setProgressBarDelay(1);

시간 단위는 밀리초(ms)입니다. 아래는 업데이트된 프로그레스 바의 모습입니다.

터보링크(Turbolinks)에서 터보(Turbo)로 마이그레이션하기 업데이트된 프로그레스 바 표시

마무리

터보에는 Streams와 Native 모듈 같은 훌륭한 기능이 아직 많이 남아 있습니다. 하지만 글의 초점을 유지하기 위해 이번 글에서는 초기 마이그레이션 단계까지만 다루었습니다.

언제나 그렇듯 공식 Turbo Handbook을 적극 추천합니다. 필자 역시 이번 마이그레이션 과정을 시작할 때 큰 도움을 받았습니다. 이곳에서 프로젝트의 특수한 상황에 대처하는 데 필요한 모든 자료를 찾을 수 있습니다.

예를 들어 앱에서 Devise를 사용하고 있다면 몇 가지 조정이 필요할 가능성이 높습니다. 다행히 터보 팀은 Devise와 터보를 함께 사용할 때의 세부 사항을 다룬 훌륭한 튜토리얼을 제공하고 있으니 참고하시기 바랍니다.