rubyonrails.org에서 더 보기:

GitHub에서 이 파일을 읽지 마세요. 가이드는 https://guides.rubyonrails.org 에서 제공됩니다.

Rails에서 JavaScript 작업하기

이 가이드는 외부 JavaScript 패키지를 사용하는 옵션과 Rails에서 Turbo를 사용하는 방법을 포함하여 Rails 애플리케이션에 JavaScript 기능을 통합하는 옵션들을 다룹니다.

이 가이드를 읽은 후, 다음 내용을 알게 될 것입니다:

  • Node.js, Yarn 또는 JavaScript bundler 없이 Rails를 사용하는 방법
  • import maps, Bun, esbuild, Rollup, 또는 Webpack을 사용하여 JavaScript를 번들링하는 새로운 Rails 애플리케이션을 생성하는 방법
  • Turbo가 무엇이고 어떻게 사용하는지
  • Rails에서 제공하는 Turbo HTML 헬퍼를 사용하는 방법

1 Import Maps

Import maps을 사용하면 브라우저에서 직접 버전이 지정된 파일에 매핑되는 논리적 이름을 사용하여 JavaScript 모듈을 가져올 수 있습니다. Import maps는 Rails 7부터 기본값이며, transpiling이나 bundling 없이도 대부분의 npm 패키지를 사용하여 현대적인 JavaScript 애플리케이션을 구축할 수 있게 해줍니다.

Import maps를 사용하는 애플리케이션은 작동을 위해 Node.jsYarn이 필요하지 않습니다. importmap-rails를 사용하여 JavaScript 의존성을 관리할 계획이라면, Node.js나 Yarn을 설치할 필요가 없습니다.

Import maps를 사용할 때는 별도의 빌드 프로세스가 필요하지 않으며, bin/rails server로 서버를 시작하기만 하면 됩니다.

1.1 importmap-rails 설치하기

Importmap for Rails는 Rails 7+ 신규 애플리케이션에 자동으로 포함되어 있지만, 기존 애플리케이션에서도 수동으로 설치할 수 있습니다:

$ bin/bundle add importmap-rails

설치 task를 실행하세요:

$ bin/rails importmap:install

1.2 importmap-rails로 npm 패키지 추가하기

import map 기반 애플리케이션에 새로운 패키지를 추가하려면 터미널에서 bin/importmap pin 명령어를 실행하세요:

$ bin/importmap pin react react-dom

그리고 평소처럼 application.js에 패키지를 import하세요:

import React from "react"
import ReactDOM from "react-dom"

2 Adding npm Packages with JavaScript Bundlers

Import maps는 새로운 Rails 애플리케이션의 기본값이지만, 전통적인 JavaScript 번들링을 선호한다면 Bun, esbuild, Webpack, 또는 Rollup.js를 선택하여 새로운 Rails 애플리케이션을 만들 수 있습니다.

새로운 Rails 애플리케이션에서 import maps 대신 bundler를 사용하려면 rails new 명령어에 --javascript 또는 -j 옵션을 전달하세요:

$ rails new my_new_app --javascript=bun
또는
$ rails new my_new_app -j bun

이러한 bundling 옵션들은 각각 간단한 설정과 jsbundling-rails gem을 통한 asset pipeline과의 통합을 제공합니다.

bundling 옵션을 사용할 때는 Rails 서버를 시작하고 개발을 위한 JavaScript를 빌드하기 위해 bin/dev를 사용하세요.

2.1 JavaScript Runtime 설치하기

Rails 애플리케이션에서 esbuild, Rollup.js, 또는 Webpack을 사용하여 JavaScript를 번들링하는 경우, Node.js와 Yarn이 설치되어 있어야 합니다. Bun을 사용하는 경우에는 JavaScript runtime과 번들러를 모두 포함하고 있으므로 Bun만 설치하면 됩니다.

2.1.1 Bun 설치하기

Bun 웹사이트에서 설치 방법을 찾아보시고, 다음 명령어를 통해 올바르게 설치되었는지와 경로가 제대로 설정되었는지 확인하세요:

$ bun --version

Bun runtime의 버전이 출력되어야 합니다. 1.0.0과 같이 표시된다면 Bun이 정상적으로 설치된 것입니다.

그렇지 않다면 현재 디렉토리에서 Bun을 재설치하거나 터미널을 다시 시작해야 할 수 있습니다.

2.1.2 Node.js와 Yarn 설치하기

esbuild, Rollup.js 또는 Webpack을 사용하는 경우 Node.js와 Yarn이 필요합니다.

Node.js 웹사이트에서 설치 지침을 확인하고 다음 명령어로 올바르게 설치되었는지 확인하세요:

$ node --version

Node.js 런타임의 버전이 출력되어야 합니다. 버전이 8.16.0 이상인지 확인하세요.

Yarn을 설치하려면 Yarn website의 설치 지침을 따르세요. 이 명령을 실행하면 Yarn 버전이 출력됩니다:

$ yarn --version

1.22.0와 같은 메시지가 표시되면, Yarn이 올바르게 설치된 것입니다.

3 Import Maps와 JavaScript Bundler 중 선택하기

새로운 Rails 애플리케이션을 만들 때, import maps와 JavaScript 번들링 솔루션 중 하나를 선택해야 합니다. 모든 애플리케이션은 서로 다른 요구사항을 가지고 있으며, JavaScript 옵션을 선택하기 전에 요구사항을 신중하게 고려해야 합니다. 크고 복잡한 애플리케이션의 경우 한 옵션에서 다른 옵션으로 마이그레이션하는 데 시간이 많이 소요될 수 있기 때문입니다.

Import maps가 기본 옵션인 이유는 Rails 팀이 복잡성 감소, 개발자 경험 개선, 성능 향상을 위한 import maps의 잠재력을 믿기 때문입니다.

대부분의 애플리케이션, 특히 JavaScript 요구사항을 위해 주로 Hotwire 스택에 의존하는 애플리케이션의 경우, import maps가 장기적으로 올바른 선택이 될 것입니다. Rails 7에서 import maps를 기본값으로 선택한 이유에 대해서는 여기에서 자세히 읽을 수 있습니다.

다른 애플리케이션들은 여전히 전통적인 JavaScript bundler가 필요할 수 있습니다. 전통적인 bundler를 선택해야 하는 요구사항은 다음과 같습니다:

  • JSX나 TypeScript와 같은 트랜스파일 단계가 필요한 경우
  • CSS를 포함하거나 Webpack loaders에 의존하는 JavaScript 라이브러리를 사용해야 하는 경우
  • tree-shaking이 반드시 필요하다고 확신하는 경우
  • cssbundling-rails gem을 통해 Bootstrap, Bulma, PostCSS, 또는 Dart CSS를 설치하는 경우. Tailwind와 Sass를 제외한 이 gem이 제공하는 모든 옵션은 rails new에서 다른 옵션을 지정하지 않으면 자동으로 esbuild를 설치합니다.

4 Turbo

import maps나 전통적인 bundler 중 어느 것을 선택하든, Rails는 애플리케이션 속도를 향상시키고 작성해야 할 JavaScript 양을 획기적으로 줄이기 위해 Turbo를 함께 제공합니다.

Turbo를 사용하면 서버가 HTML을 직접 전달할 수 있어, Rails 애플리케이션의 서버 측을 단순한 JSON API로 축소시키는 기존의 프론트엔드 프레임워크에 대한 대안이 됩니다.

4.1 Turbo Drive

Turbo Drive는 모든 탐색 요청에서 전체 페이지의 해체와 재구축을 피함으로써 페이지 로딩 속도를 향상시킵니다. Turbo Drive는 Turbolinks를 개선하고 대체한 것입니다.

4.2 Turbo Frames

Turbo Frames는 페이지의 나머지 콘텐츠에 영향을 주지 않고 미리 정의된 페이지의 일부를 요청 시 업데이트할 수 있게 해줍니다.

Turbo Frames를 사용하면 커스텀 JavaScript 없이 즉석 편집을 구현하고, 콘텐츠를 지연 로드하고, 서버에서 렌더링되는 탭 인터페이스를 쉽게 만들 수 있습니다.

Rails는 turbo-rails gem을 통해 Turbo Frames의 사용을 단순화하는 HTML 헬퍼를 제공합니다.

이 gem을 사용하면 다음과 같이 turbo_frame_tag 헬퍼로 애플리케이션에 Turbo Frame을 추가할 수 있습니다:

<%= turbo_frame_tag dom_id(post) do %>
  <div>
     <%= link_to post.title, post_path(post) %>
  </div>
<% end %>

4.3 Turbo Streams

Turbo Streams는 자체 실행되는 <turbo-stream> 요소로 감싸진 HTML 조각으로 페이지 변경사항을 전달합니다. Turbo Streams를 사용하면 WebSocket을 통해 다른 사용자가 만든 변경사항을 브로드캐스트하고, 전체 페이지를 로드하지 않고도 form 제출 후에 페이지의 일부를 업데이트할 수 있습니다.

Rails는 turbo-rails gem을 통해 Turbo Streams의 사용을 단순화하는 HTML과 서버 사이드 헬퍼를 제공합니다.

이 gem을 사용하면 controller action에서 Turbo Streams를 렌더링할 수 있습니다:

def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.turbo_stream
    else
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end

Rails는 자동으로 .turbo_stream.erb 뷰 파일을 찾아 해당 뷰를 발견하면 렌더링합니다.

Turbo Stream 응답은 controller action에서 인라인으로 렌더링할 수도 있습니다:

def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.turbo_stream { render turbo_stream: turbo_stream.prepend("posts", partial: "post") }
      # posts 요소의 맨 앞에 post partial을 turbo_stream 응답으로 추가합니다
    else
      format.html { render :new, status: :unprocessable_entity }
      # 저장 실패시 new 템플릿을 422 상태코드와 함께 렌더링합니다
    end
  end
end

마지막으로, Turbo Streams는 내장된 helper를 사용하여 model이나 background job에서 시작될 수 있습니다. 이러한 broadcast는 WebSocket 연결을 통해 모든 사용자에게 content를 업데이트하는데 사용될 수 있으며, 페이지 content를 최신 상태로 유지하고 애플리케이션에 생동감을 불어넣습니다.

model에서 Turbo Stream을 broadcast하려면 다음과 같이 model callback을 결합하세요:

class Post < ApplicationRecord
  after_create_commit { broadcast_append_to("posts") }
end

다음과 같이 업데이트를 수신해야 하는 페이지에 WebSocket 연결이 설정되어 있는 경우:

<%= turbo_stream_from "posts" %>

에서 posts라는 이름의 Turbo Stream을 구독합니다.

5 Replacements for Rails/UJS Functionality

Rails 6은 UJS(Unobtrusive JavaScript)라고 불리는 도구와 함께 제공되었습니다. UJS는 개발자가 <a> 태그의 HTTP 요청 메서드를 재정의하고, 작업을 실행하기 전에 확인 대화 상자를 추가하는 등의 기능을 제공합니다. UJS는 Rails 7 이전의 기본값이었지만, 이제는 대신 Turbo를 사용하는 것이 권장됩니다.

5.1 Method

링크를 클릭하면 항상 HTTP GET request가 발생합니다. 만약 여러분의 애플리케이션이 RESTful하다면, 일부 링크들은 사실상 서버의 데이터를 변경하는 action이며, non-GET request로 수행되어야 합니다. data-turbo-method attribute를 사용하면 이러한 링크를 "post", "put" 또는 "delete"와 같은 명시적인 method로 표시할 수 있습니다.

Turbo는 애플리케이션에서 <a> 태그의 turbo-method data attribute를 스캔하고, 해당 attribute가 존재할 경우 기본 GET action을 재정의하여 지정된 method를 사용합니다.

예시:

<%= link_to "게시물 삭제", post_path(post), data: { turbo_method: "delete" } %>

다음과 같이 생성됩니다:

[이것이 전부입니다.]

<a data-turbo-method="delete" href="...">게시물 삭제</a>

data-turbo-method로 링크의 메서드를 변경하는 대신 Rails의 button_to 헬퍼를 사용할 수 있습니다. 접근성 측면에서 GET이 아닌 동작에는 실제 버튼과 폼을 사용하는 것이 더 바람직합니다.

5.2 Confirmations

data-turbo-confirm 속성을 links와 forms에 추가하여 사용자에게 추가 확인을 요청할 수 있습니다. link를 클릭하거나 form을 제출할 때, 사용자에게 해당 속성의 텍스트가 포함된 JavaScript confirm() 대화상자가 표시됩니다. 사용자가 취소를 선택하면 해당 동작이 실행되지 않습니다.

예를 들어, link_to helper의 경우:

<%= link_to "글 삭제", post_path(post), data: { turbo_method: "delete", turbo_confirm: "정말 삭제하시겠습니까?" } %>

다음을 생성합니다:

<a href="..." data-turbo-confirm="정말로 하시겠습니까?" data-turbo-method="delete">게시물 삭제</a>

사용자가 "Delete post" 링크를 클릭하면 "확실합니까?"라는 확인 대화 상자가 표시됩니다.

이 attribute는 button_to helper에서도 사용할 수 있지만, button_to helper가 내부적으로 생성하는 form에 추가되어야 합니다.

<%= button_to "게시글 삭제", post, method: :delete, form: { data: { turbo_confirm: "삭제하시겠습니까?" } } %>

5.3 Ajax Requests

JavaScript에서 GET이 아닌 요청을 할 때는 X-CSRF-Token 헤더가 필요합니다. 이 헤더가 없으면 Rails는 요청을 수락하지 않습니다.

이 토큰은 Rails에서 Cross-Site Request Forgery (CSRF) 공격을 방지하기 위해 필요합니다. 자세한 내용은 보안 가이드에서 확인하세요.

Rails Request.JS는 Rails에서 필요한 request 헤더를 추가하는 로직을 캡슐화합니다. 패키지에서 FetchRequest 클래스를 import하고 request 메서드, url, options를 전달하여 인스턴스화한 다음, await request.perform()을 호출하고 response로 필요한 작업을 수행하면 됩니다.

예를 들면:

import { FetchRequest } from '@rails/request.js'

....

async myMethod () {
  const request = new FetchRequest('post', 'localhost:3000/posts', {
    body: JSON.stringify({ name: 'Request.JS' })
  }) // post 요청을 localhost:3000/posts로 보내고 body에 JSON 형식의 데이터를 추가하는 새로운 FetchRequest를 생성
  const response = await request.perform() // 요청 실행
  if (response.ok) { // 응답이 성공적인 경우
    const body = await response.text // 응답 본문을 텍스트로 가져옴
  }
}

Ajax 호출을 하기 위해 다른 라이브러리를 사용할 때는 security token을 default header로 직접 추가해야 합니다. token을 얻으려면 application view에서 csrf_meta_tags가 생성한 <meta name='csrf-token' content='THE-TOKEN'> 태그를 확인하세요. 다음과 같이 할 수 있습니다:

document.head.querySelector("meta[name=csrf-token]")?.content


맨 위로