1 Asset Pipeline이란 무엇인가?
Asset pipeline은 JavaScript와 CSS asset의 전달을 처리하기 위한 프레임워크를 제공합니다. 이는 HTTP/2와 같은 기술과 연결(concatenation) 및 축소(minification)와 같은 기법을 활용하여 수행됩니다. 마지막으로, 애플리케이션이 다른 gem의 asset과 자동으로 결합될 수 있도록 합니다.
Asset pipeline은 importmap-rails, sprockets, sprockets-rails gem들로 구현되며 기본적으로 활성화되어 있습니다. 새로운 애플리케이션을 생성할 때 --skip-asset-pipeline
옵션을 전달하여 이를 비활성화할 수 있습니다.
$ rails new appname --skip-asset-pipeline
이 가이드는 CSS는 sprockets
를, JavaScript 처리는 importmap-rails
만을 사용하는 기본 asset pipeline에 중점을 둡니다. 이 두 가지의 주요 제한사항은 transpiling을 지원하지 않아서 Babel, TypeScript, Sass, React JSX format, Tailwind CSS와 같은 것들을 사용할 수 없다는 점입니다. JavaScript/CSS를 위한 transpiling이 필요하다면 Alternative Libraries 섹션을 읽어보시기를 권장합니다.
2 주요 기능
asset pipeline의 첫 번째 기능은 각 파일 이름에 SHA256 fingerprint를 삽입하여 웹 브라우저와 CDN에서 파일을 캐시할 수 있도록 하는 것입니다. 이 fingerprint는 파일 내용을 변경할 때 자동으로 업데이트되어 캐시를 무효화합니다.
asset pipeline의 두 번째 기능은 JavaScript 파일을 제공할 때 import maps를 사용하는 것입니다. 이를 통해 transpiling과 bundling 없이도 ES modules(ESM)용으로 만들어진 JavaScript 라이브러리를 사용하여 현대적인 애플리케이션을 구축할 수 있습니다. 결과적으로 Webpack, yarn, node 또는 다른 JavaScript 도구들이 필요하지 않게 됩니다.
asset pipeline의 세 번째 기능은 모든 CSS 파일을 하나의 main .css
파일로 연결하고, 이를 minify하거나 압축하는 것입니다. 이 가이드의 뒷부분에서 배우게 될 내용처럼, 원하는 방식으로 파일을 그룹화하도록 이 전략을 커스터마이즈할 수 있습니다. 프로덕션 환경에서 Rails는 각 파일 이름에 SHA256 fingerprint를 삽입하여 웹 브라우저에서 파일이 캐시되도록 합니다. 이 fingerprint를 변경하여 캐시를 무효화할 수 있으며, 파일 내용을 변경할 때마다 자동으로 이루어집니다.
asset pipeline의 네 번째 기능은 CSS를 위한 상위 레벨 언어를 통해 assets를 코딩할 수 있게 해준다는 것입니다.
2.1 Fingerprinting이란 무엇이고 왜 중요할까요?
Fingerprinting은 파일의 이름을 파일의 내용에 따라 결정하는 기술입니다. 파일 내용이 변경되면 파일 이름도 같이 변경됩니다. 정적이거나 자주 변경되지 않는 컨텐츠의 경우, 서로 다른 서버나 배포 날짜에 관계없이 두 버전의 파일이 동일한지 쉽게 확인할 수 있는 방법을 제공합니다.
파일 이름이 고유하고 컨텐츠를 기반으로 할 때, HTTP 헤더를 설정하여 모든 캐시(CDN, ISP, 네트워크 장비 또는 웹 브라우저)가 컨텐츠의 자체 복사본을 유지하도록 할 수 있습니다. 컨텐츠가 업데이트되면 fingerprint가 변경됩니다. 이로 인해 원격 클라이언트가 새로운 컨텐츠 복사본을 요청하게 됩니다. 이는 일반적으로 cache busting이라고 알려져 있습니다.
Sprockets가 fingerprinting에 사용하는 기술은 보통 파일 이름 끝에 컨텐츠의 해시를 삽입하는 것입니다. 예를 들어 CSS 파일 global.css
global-908e25f4bf641868d8683022a5b62f54.css
Rails asset pipeline이 채택한 전략입니다.
Fingerprinting은 development와 production 환경 모두에서 기본적으로 활성화되어 있습니다. config.assets.digest
옵션을 통해 설정에서 이를 활성화하거나 비활성화할 수 있습니다.
2.2 Import Maps란 무엇이며 왜 중요할까요?
Import maps를 사용하면 브라우저에서 직접 논리적 이름을 사용하여 버전이 지정된/다이제스트된 파일에 매핑되는 JavaScript 모듈을 가져올 수 있습니다. 따라서 트랜스파일이나 번들링 없이도 ES modules(ESM)용으로 만들어진 JavaScript 라이브러리를 사용하여 최신 JavaScript 애플리케이션을 구축할 수 있습니다.
이 방식을 사용하면 하나의 큰 JavaScript 파일 대신 많은 작은 JavaScript 파일들을 제공하게 됩니다. HTTP/2 덕분에 더 이상 초기 전송 과정에서 실질적인 성능 저하가 발생하지 않으며, 실제로 더 나은 캐싱 동작으로 인해 장기적으로 상당한 이점을 제공합니다.
3 JavaScript Asset Pipeline으로 Import Maps를 사용하는 방법
Import Maps는 기본 JavaScript 프로세서이며, import maps 생성 로직은 importmap-rails
gem에서 처리됩니다.
Import maps는 JavaScript 파일에만 사용되며 CSS 전달에는 사용할 수 없습니다. CSS에 대해 알아보려면 Sprockets 섹션을 확인하세요.
Gem 홈페이지에서 자세한 사용 방법을 확인할 수 있지만, importmap-rails
의 기본 사항을 이해하는 것이 중요합니다.
3.1 동작 방식
Import maps는 본질적으로 "bare module specifiers"라고 불리는 것에 대한 문자열 대체입니다. Import maps를 사용하면 JavaScript 모듈 import 이름을 표준화할 수 있습니다.
예를 들어 다음과 같은 import 정의는 import map 없이는 동작하지 않습니다:
import React from "react"
작동하도록 하려면 다음과 같이 정의해야 합니다:
import React from "https://ga.jspm.io/npm:react@17.0.2/index.js"
import map이 나오면, react
이름을 https://ga.jspm.io/npm:react@17.0.2/index.js
주소에 고정하도록 정의합니다. 이러한 정보를 통해 브라우저는 단순화된 import React from "react"
정의를 받아들입니다. import map을 라이브러리 소스 주소의 별칭이라고 생각하면 됩니다.
3.2 사용법
importmap-rails
를 사용하면 라이브러리 경로를 이름에 연결하는 importmap 설정 파일을 생성할 수 있습니다:
# config/importmap.rb
pin "application"
pin "react", to: "https://ga.jspm.io/npm:react@17.0.2/index.js"
설정된 모든 import map들은 <%= javascript_importmap_tags %>
를 추가하여 애플리케이션의 <head>
요소에 첨부되어야 합니다. javascript_importmap_tags
는 head
요소에 여러 스크립트들을 렌더링합니다:
- 모든 설정된 import map들이 포함된 JSON:
<script type="importmap">
{
"imports": {
"application": "/assets/application-39f16dc3f3....js"
"react": "https://ga.jspm.io/npm:react@17.0.2/index.js"
}
}
</script>
app/javascript/application.js
에서 JavaScript를 로드하기 위한 엔트리포인트:
<script type="module">import "application"</script>
v2.0.0 이전에는 importmap-rails
가 구형 브라우저에서 import maps 지원을 보장하기 위한 폴리필로 Es-module-shims
를 javascript_importmap_tags
의 출력에 포함시켰습니다. 하지만 모든 주요 브라우저에서 import maps에 대한 네이티브 지원이 이루어짐에 따라 v2.0.0에서는 번들된 shim을 제거했습니다. import maps를 지원하지 않는 레거시 브라우저를 지원하려면, javascript_importmap_tags
이전에 Es-module-shims
를 수동으로 삽입하세요. 자세한 내용은 importmap-rails의 README를 참조하세요.
3.3 JavaScript CDN을 통한 npm 패키지 사용하기
importmap-rails
설치의 일부로 추가된 bin/importmap
명령을 사용하여 import map에서 npm 패키지를 pin, unpin 또는 업데이트할 수 있습니다. 이 binstub는 JSPM.org
를 사용합니다.
작동 방식은 다음과 같습니다:
$ bin/importmap pin react react-dom
"react"를 https://ga.jspm.io/npm:react@17.0.2/index.js에 고정합니다
"react-dom"을 https://ga.jspm.io/npm:react-dom@17.0.2/index.js에 고정합니다
"object-assign"을 https://ga.jspm.io/npm:object-assign@4.1.1/index.js에 고정합니다
"scheduler"를 https://ga.jspm.io/npm:scheduler@0.20.2/index.js에 고정합니다
bin/importmap json
{
"imports": {
"application": "/assets/application-37f365cbecf1fa2810a8303f4b6571676fa1f9c56c248528bc14ddb857531b95.js",
"react": "https://ga.jspm.io/npm:react@17.0.2/index.js",
"react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/index.js",
"object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js",
"scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/index.js"
}
}
보시다시피 jspm 기본 설정을 통해 react와 react-dom 두 패키지는 총 네 개의 의존성으로 해석됩니다.
이제 다른 모듈과 마찬가지로 application.js
진입점에서 이것들을 사용할 수 있습니다:
import React from "react"
import ReactDOM from "react-dom"
특정 version을 지정하여 pin할 수도 있습니다:
$ bin/importmap pin react@17.0.1
"react"를 https://ga.jspm.io/npm:react@17.0.1/index.js에 pinning 합니다
"object-assign"를 https://ga.jspm.io/npm:object-assign@4.1.1/index.js에 pinning 합니다
또는 pin들을 제거하는 것도 가능합니다:
$ bin/importmap unpin react
"react" unpin 처리 중
"object-assign" unpin 처리 중
별도의 "production"(기본값)과 "development" 빌드가 있는 패키지의 경우 다음과 같이 환경을 제어할 수 있습니다:
$ bin/importmap pin react --env development
"react"를 https://ga.jspm.io/npm:react@17.0.2/dev.index.js에 고정
"object-assign"를 https://ga.jspm.io/npm:object-assign@4.1.1/index.js에 고정
또한 pinning할 때 unpkg
나 jsdelivr
와 같은 대체 지원되는 CDN provider를 선택할 수 있습니다 (jspm
이 기본값입니다):
$ bin/importmap pin react --from jsdelivr
"react"를 https://cdn.jsdelivr.net/npm/react@17.0.2/index.js에 pin하는 중
다만, 한 provider에서 다른 provider로 pin을 전환하는 경우, 첫 번째 provider에서 추가된 의존성 중 두 번째 provider에서 사용하지 않는 것들을 정리해야 할 수도 있다는 점을 기억하세요.
bin/importmap
을 실행하여 모든 옵션을 확인할 수 있습니다.
이 명령어는 논리적 패키지 이름을 CDN URL로 변환하는 편의 wrapper일 뿐입니다. CDN URL을 직접 찾아서 pin을 추가할 수도 있습니다. 예를 들어, React를 위해 Skypack을 사용하고 싶다면, config/importmap.rb
에 다음과 같이 추가할 수 있습니다:
pin "react"를 "https://cdn.skypack.dev/react"에 고정
3.4 Preloading pinned modules
브라우저가 가장 깊이 중첩된 import에 도달하기 전에 파일을 하나씩 로드해야 하는 폭포 효과를 피하기 위해, importmap-rails는 modulepreload links를 지원합니다. 핀된 모듈은 pin에 preload: true
를 추가하여 preload할 수 있습니다.
앱 전체에서 사용되는 library나 framework를 preload하는 것이 좋습니다. 이렇게 하면 브라우저에게 더 빨리 다운로드하도록 지시할 수 있습니다.
예시:
# config/importmap.rb
pin "@github/hotkey", to: "https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js", preload: true
pin "md5", to: "https://cdn.jsdelivr.net/npm/md5@2.3.0/md5.js"
<%# app/views/layouts/application.html.erb %>
<%= javascript_importmap_tags %>
<%# importmap이 설정되기 전에 다음 link를 포함할 것입니다: %>
<link rel="modulepreload" href="https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js">
...
가장 최신 문서는 importmap-rails
저장소를 참조하세요.
4 Sprockets 사용 방법
웹에 애플리케이션 자산을 노출하는 가장 단순한 접근 방식은 public
폴더의 하위 디렉토리인 images
와 stylesheets
같은 곳에 저장하는 것입니다. 하지만 대부분의 현대 웹 애플리케이션은 자산을 압축하고 지문을 추가하는 등 특정한 방식으로 처리해야 하기 때문에 수동으로 이를 수행하기는 어렵습니다.
Sprockets는 설정된 디렉토리에 저장된 자산을 자동으로 전처리하고, 처리 후에는 지문 처리, 압축, 소스맵 생성 및 기타 설정 가능한 기능들과 함께 public/assets
폴더에 노출하도록 설계되었습니다.
자산은 여전히 public
계층 구조에 배치될 수 있습니다. config.public_file_server.enabled
가 true로 설정되면 public
아래의 모든 자산은 애플리케이션이나 웹 서버에 의해 정적 파일로 제공됩니다. 제공되기 전에 전처리가 필요한 파일들에 대해서는 manifest.js
지시문을 정의해야 합니다.
프로덕션 환경에서 Rails는 기본적으로 이러한 파일들을 public/assets
에 미리 컴파일합니다. 미리 컴파일된 사본들은 웹 서버에 의해 정적 자산으로 제공됩니다. app/assets
의 파일들은 프로덕션 환경에서 절대 직접 제공되지 않습니다.
4.1 Manifest 파일과 Directive
Sprockets로 asset들을 컴파일할 때, Sprockets는 일반적으로 application.css
와 이미지와 같은 최상위 대상을 컴파일할지 결정해야 합니다. 최상위 대상들은 Sprockets의 manifest.js
파일에 정의되며, 기본적으로 다음과 같습니다:
//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js
이 매니페스트는:
- 모든 images 폴더의 asset들을 연결합니다
- stylesheets 폴더의 모든 CSS 파일들을 연결합니다
- JavaScript 디렉토리의 모든 JavaScript 파일들을 연결합니다
- vendor/javascript 디렉토리의 모든 JavaScript 파일들을 연결합니다
directive - Sprockets에게 단일 CSS 또는 JavaScript 파일을 빌드하기 위해 필요한 파일을 지시하는 명령어를 포함하고 있습니다.
이는 ./app/assets/images
디렉토리나 모든 하위 디렉토리에 있는 모든 파일의 내용과 ./app/javascript
또는 ./vendor/javascript
에서 직접 JS로 인식되는 모든 파일을 포함하기 위한 것입니다.
./app/assets/stylesheets
디렉토리에 있는 모든 CSS를 로드합니다(하위 디렉토리는 제외). ./app/assets/stylesheets
폴더에 application.css
와 marketing.css
파일이 있다고 가정하면, 뷰에서 <%= stylesheet_link_tag "application" %>
또는 <%= stylesheet_link_tag "marketing" %>
로 이러한 스타일시트를 로드할 수 있습니다.
기본적으로 JavaScript 파일들이 assets
디렉토리에서 로드되지 않는 것을 알 수 있는데, 이는 ./app/javascript
가 importmap-rails
gem의 기본 진입점이고 vendor
폴더는 다운로드된 JS 패키지가 저장되는 곳이기 때문입니다.
manifest.js
에서 전체 디렉토리 대신 특정 파일을 로드하기 위해 link
directive를 지정할 수도 있습니다. link
directive는 명시적인 파일 확장자를 제공해야 합니다.
Sprockets는 지정된 파일들을 로드하고, 필요한 경우 처리하며, 하나의 단일 파일로 연결한 다음 압축합니다(config.assets.css_compressor
또는 config.assets.js_compressor
의 값에 기반). 압축은 파일 크기를 줄여 브라우저가 파일을 더 빠르게 다운로드할 수 있게 합니다.
4.2 Controller Specific Assets
Controller를 생성하거나 scaffold를 생성할 때, Rails는 해당 controller를 위한 Cascading Style Sheet 파일도 생성합니다. 또한, scaffold를 생성할 때는 scaffolds.css
파일도 생성됩니다.
예를 들어, ProjectsController
를 생성하면 Rails는 app/assets/stylesheets/projects.css
에 새 파일을 추가합니다. 기본적으로 이 파일들은 manifest.js
파일의 link_directory
지시어를 통해 애플리케이션에서 즉시 사용할 수 있습니다.
또한 다음과 같이 각각의 controller에서만 controller별 스타일시트 파일을 포함하도록 선택할 수도 있습니다:
<%= stylesheet_link_tag params[:controller] %>
이렇게 할 때는 application.css
에서 require_tree
지시문을 사용하지 않도록 해야 합니다. 그렇지 않으면 controller별 asset들이 두 번 이상 포함될 수 있기 때문입니다.
4.3 Asset 구성 방법
Pipeline asset들은 애플리케이션 내에서 다음 세 위치 중 하나에 배치할 수 있습니다:
app/assets
, lib/assets
또는 vendor/assets
.
app/assets
는 사용자 정의 이미지나 stylesheet 같이 애플리케이션이 소유한 asset들을 위한 곳입니다.app/javascript
는 JavaScript 코드를 위한 곳입니다vendor/[assets|javascript]
는 CSS framework나 JavaScript 라이브러리와 같이 외부 엔티티가 소유한 asset들을 위한 곳입니다. asset Pipeline에서 처리되는 다른 파일(이미지, stylesheet 등)을 참조하는 써드파티 코드는asset_path
와 같은 헬퍼를 사용하도록 다시 작성되어야 한다는 점을 유의하세요.
다른 위치들은 manifest.js
파일에서 구성할 수 있으며, Manifest 파일과 지시자를 참조하세요.
4.3.1 검색 경로
manifest나 헬퍼에서 파일이 참조될 때, Sprockets는 manifest.js
에 지정된 모든 위치에서 해당 파일을 검색합니다. Rails 콘솔에서 Rails.application.config.assets.paths
를 검사하여 검색 경로를 볼 수 있습니다.
4.3.2 폴더의 프록시로 인덱스 파일 사용하기
Sprockets는 index
라는 이름의 파일(관련 확장자 포함)을 특별한 목적으로 사용합니다.
예를 들어, lib/assets/stylesheets/library_name
에 저장된 많은 모듈이 있는 CSS 라이브러리가 있다면, lib/assets/stylesheets/library_name/index.css
파일은 이 라이브러리의 모든 파일에 대한 manifest 역할을 합니다. 이 파일은 필요한 모든 파일의 순서가 있는 목록이나 간단한 require_tree
지시자를 포함할 수 있습니다.
이는 public/library_name/index.html
의 파일이 /library_name
요청으로 접근할 수 있는 방식과 유사합니다. 이는 index 파일을 직접 사용할 수 없다는 것을 의미합니다.
라이브러리 전체는 .css
파일에서 다음과 같이 접근할 수 있습니다:
/* ...
*= require library_name
*/
이는 관련 코드를 다른 곳에서 포함하기 전에 그룹화할 수 있도록 하여 유지보수를 단순화하고 깔끔하게 유지합니다.
4.4 자산 링크 코딩하기
Sprockets는 여러분의 자산에 접근하기 위한 어떤 새로운 메서드도 추가하지 않습니다 - 여러분은 여전히 친숙한 stylesheet_link_tag
를 사용합니다:
<%= stylesheet_link_tag "application", media: "all" %>
Rails
에 기본적으로 포함되어 있는 turbo-rails
gem을 사용하는 경우, data-turbo-track
옵션을 포함하면 Turbo가 asset이 업데이트되었는지 확인하고 업데이트된 경우 페이지에 로드합니다:
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
일반 view에서는 다음과 같이 app/assets/images
디렉토리에 있는 이미지들에 접근할 수 있습니다:
<%= image_tag "rails.png" %>
Sprockets pipeline이 애플리케이션에서 활성화되어 있고(현재 환경 컨텍스트에서 비활성화되지 않은 경우), 이 파일은 Sprockets에 의해 제공됩니다. public/assets/rails.png
에 파일이 존재하는 경우 웹 서버에 의해 제공됩니다.
또는 public/assets/rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png
와 같이 SHA256 해시가 포함된 파일 요청도 동일한 방식으로 처리됩니다. 이러한 해시가 어떻게 생성되는지는 이 가이드의 후반부 In Production 섹션에서 다룹니다.
필요한 경우 이미지를 하위 디렉토리로 구성할 수도 있으며, 태그에서 디렉토리 이름을 지정하여 접근할 수 있습니다:
<%= image_tag "icons/rails.png" %>
asset들을 precompile하는 경우(In Production
아래 참조), 존재하지 않는 asset에 대한 링크는 호출하는 페이지에서 예외를 발생시킵니다. 여기에는 빈 문자열에 대한 링크도 포함됩니다. 따라서 사용자가 제공한 데이터와 함께 image_tag
및 다른 helper들을 사용할 때 주의해야 합니다.
4.4.1 CSS와 ERB
asset pipeline은 자동으로 ERB를 평가합니다. 이는 CSS asset에 erb
확장자를 추가하면(예: application.css.erb
), asset_path
와 같은 helper들을 CSS 규칙 내에서 사용할 수 있다는 것을 의미합니다:
.class { background-image: url(<%= asset_path 'image.png' %>) }
이는 참조되는 특정 asset의 경로를 작성합니다. 이 예시에서는 app/assets/images/image.png
와 같은 asset load path 중 하나에 이미지가 있는 것이 합리적일 것입니다. 만약 이 이미지가 이미 public/assets
에 fingerprinted 파일로 존재한다면, 해당 경로가 참조됩니다.
만약 CSS 파일에 이미지 데이터를 직접 임베딩하는 방식인 data URI를 사용하고 싶다면, asset_data_uri
헬퍼를 사용할 수 있습니다.
#logo { background: url(<%= asset_data_uri 'logo.png' %>) }
CSS 소스에 올바른 형식의 data URI를 삽입합니다.
닫는 태그는 -%>
스타일이 될 수 없다는 점에 유의하세요.
4.5 Asset를 찾지 못했을 때 에러 발생시키기
sprockets-rails >= 3.2.0을 사용하고 있다면 asset 조회가 수행되었는데 아무것도 찾지 못했을 때 어떻게 할지 설정할 수 있습니다. "asset fallback"을 끄면 asset를 찾을 수 없을 때 에러가 발생합니다.
config.assets.unknown_asset_fallback = false
알 수 없는 asset이 누락되었을 때 fallback을 허용할지 여부를 지정합니다. 기본적으로 production에서는 true이고 development에서는 false입니다. 이 설정을 false로 하면 알 수 없는 asset은 예외를 발생시킵니다.
"asset fallback"이 활성화되어 있으면 asset을 찾을 수 없을 때 오류를 발생시키는 대신 경로가 출력됩니다. asset fallback 동작은 기본적으로 비활성화되어 있습니다.
4.6 Digest 끄기
config/environments/development.rb
를 다음과 같이 업데이트하여 digest를 끌 수 있습니다:
config.assets.digest = false
assets 파일명에 MD5 fingerprint를 추가하지 않습니다.
이 옵션이 true이면 asset URL에 대한 digest가 생성됩니다.
4.7 Source Maps 켜기
config/environments/development.rb
에서 다음 내용을 포함하여 source maps를 켤 수 있습니다:
config.assets.debug = true
이는 개별 개발 assets를 로드하는 source tags 를 출력하기 위한 것입니다. Sprockets는 application.css와 application.js 파일에서 요청된 모든 asset을 bundling하는 대신, 각 파일에 대해 개별 include line을 output합니다.
디버그 모드가 켜져있을 때, Sprockets는 각 asset에 대한 Source Map을 생성합니다. 이를 통해 브라우저의 개발자 도구에서 각 파일을 개별적으로 디버깅할 수 있습니다.
Asset들은 서버가 시작된 후 첫 번째 요청에서 컴파일되고 캐시됩니다. Sprockets는 후속 요청의 오버헤드를 줄이기 위해 must-revalidate
Cache-Control HTTP 헤더를 설정합니다 - 이러한 요청에서 브라우저는 304(Not Modified) 응답을 받습니다.
요청 사이에 manifest 파일 중 어느 하나라도 변경되면, 서버는 새로 컴파일된 파일로 응답합니다.
5 Production 환경에서
Production 환경에서 Sprockets는 위에서 설명한 fingerprinting 방식을 사용합니다. 기본적으로 Rails는 asset들이 사전 컴파일되어 있고 웹 서버에 의해 정적 asset으로 제공될 것이라고 가정합니다.
사전 컴파일 단계에서 컴파일된 파일의 내용으로부터 SHA256이 생성되며, 파일들이 디스크에 작성될 때 파일명에 삽입됩니다. 이러한 fingerprinted 이름들은 manifest 이름 대신 Rails 헬퍼에서 사용됩니다.
예를 들어 다음과 같습니다:
<%= stylesheet_link_tag "application" %>
다음과 같은 것을 생성합니다:
<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" rel="stylesheet" />
fingerprinting 동작은 config.assets.digest
초기화 옵션으로 제어됩니다(기본값은 true
).
일반적인 상황에서는 기본 config.assets.digest
옵션을 변경해서는 안 됩니다. 파일명에 digest가 없고 far-future 헤더가 설정된 경우, 원격 클라이언트는 컨텐츠가 변경되었을 때 파일을 다시 가져와야 한다는 것을 알 수 없습니다.
5.1 Precompiling Assets
Rails는 asset manifest와 pipeline의 다른 파일들을 컴파일하기 위한 명령어가 함께 제공됩니다.
컴파일된 asset들은 config.assets.prefix
에 지정된 위치에 작성됩니다.
기본적으로 /assets
디렉토리입니다.
서버에서 배포하는 동안 이 명령어를 호출하여 서버에서 직접 컴파일된 버전의 asset을 생성할 수 있습니다. 로컬에서 컴파일하는 방법에 대한 정보는 다음 섹션을 참조하세요.
명령어는 다음과 같습니다:
$ RAILS_ENV=production rails assets:precompile
이는 config.assets.prefix
에 지정된 폴더를 shared/assets
에 연결합니다.
이미 이 공유 폴더를 사용하고 있다면 직접 배포 명령어를 작성해야 합니다.
캐시된 페이지의 수명 동안 이전에 컴파일된 asset들을 참조하는 원격 캐시 페이지가 계속 작동하도록 하기 위해서는 이 폴더가 배포간에 공유되는 것이 중요합니다.
항상 .js
또는 .css
로 끝나는 예상 컴파일 파일명을 지정하세요.
이 명령은 또한 모든 asset과 각각의 fingerprint를 포함하는 목록이 들어있는 .sprockets-manifest-randomhex.json
(randomhex
는 16바이트 랜덤 16진수 문자열)을 생성합니다. 이는 매핑 요청을 Sprockets로 다시 전달하지 않도록 Rails 헬퍼 메소드에서 사용됩니다. 일반적인 manifest 파일은 다음과 같습니다:
{"files":{"application-<fingerprint>.js":{"logical_path":"application.js","mtime":"2016-12-23T20:12:03-05:00","size":412383,
"digest":"<fingerprint>","integrity":"sha256-<random-string>"}},
"assets":{"application.js":"application-<fingerprint>.js"}}
애플리케이션에서는 manifest에 더 많은 파일과 asset이 나열되며, <fingerprint>
와 <random-string>
도 생성됩니다.
manifest의 기본 위치는 config.assets.prefix
에 지정된 경로(기본값은 '/assets')의 루트입니다.
production 환경에서 precompile된 파일이 누락된 경우, 누락된 파일의 이름을 나타내는 Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError
예외가 발생합니다.
5.1.1 Far-future Expires Header
Precompile된 asset들은 파일 시스템에 존재하며 웹 서버에서 직접 제공됩니다. 이들은 기본적으로 far-future header를 가지고 있지 않으므로, fingerprinting의 이점을 얻으려면 이러한 header를 추가하도록 서버 설정을 업데이트해야 합니다.
Apache의 경우:
# Expires* 지시어들은 Apache 모듈인
# `mod_expires`가 활성화되어 있어야 합니다.
<Location /assets/>
# Last-Modified가 있을 때는 ETag 사용이 권장되지 않습니다
Header unset ETag
FileETag None
# RFC는 1년만 캐시하도록 규정합니다
ExpiresActive On
ExpiresDefault "access plus 1 year"
</Location>
NGINX의 경우:
upstream myapp {
# "unix" is needed for Linux/Mac OS X
server unix:///path/to/myapp/tmp/sockets/puma.sock fail_timeout=0;
# server 127.0.0.1:3000 fail_timeout=0; # for TCP
}
server {
listen 80 default_server deferred;
server_name example.com;
root /path/to/myapp/public;
try_files $uri/index.html $uri @myapp;
location @myapp {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://myapp;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
}
Docker와 함께 사용할 때는 다음과 비슷한 설정을 사용할 수 있습니다:
upstream myapp {
# server는 docker-compose.yml에 정의된 웹 컨테이너의 DNS 이름과 일치해야 합니다
server web:3000;
}
server {
listen 80;
server_name localhost;
# ~2 seconds는 502 에러 대신 클라이언트에게 504 타임아웃을 보내기 시작하는 시점입니다
# Rails 앱이 느린 경우 이 값을 늘릴 수 있습니다
proxy_read_timeout 2;
location / {
proxy_pass http://myapp;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
프로덕션 환경에서 NGINX를 사용할 때는 NGINX 공식 문서에서 공통적인 pitfall
과 그 해결책을 읽어보는 것이 좋습니다.
location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header ETag "";
}
assets로 시작하는 경로에 대해 1년간의 만료기간을 설정하고, Cache-Control 헤더를 public으로 설정하며, ETag 헤더를 빈 값으로 추가하는 nginx 설정입니다.
5.2 Local Precompilation
때로는 production 서버에서 asset을 컴파일하고 싶지 않거나 할 수 없는 경우가 있습니다. 예를 들어, production 파일시스템에 대한 쓰기 권한이 제한되어 있거나, asset을 변경하지 않고 자주 배포할 계획이 있을 수 있습니다.
이러한 경우에는 asset을 로컬에서 미리 컴파일할 수 있습니다 - 즉, 완성된 컴파일된 production-ready asset 세트를 production에 푸시하기 전에 소스 코드 저장소에 추가할 수 있습니다. 이렇게 하면 각 배포마다 production 서버에서 별도로 precompile할 필요가 없습니다.
위와 같이 다음을 사용하여 이 단계를 수행할 수 있습니다
$ RAILS_ENV=production rails assets:precompile
다음과 같은 주의사항에 유의하세요:
precompile된 asset이 있다면 개발 서버에서조차도 원본(컴파일되지 않은) asset과 더 이상 일치하지 않더라도 해당 asset이 제공됩니다.
개발 서버가 항상 asset을 실시간으로 컴파일하도록 보장하기 위해서는(따라서 항상 코드의 최신 상태를 반영하기 위해서는), development 환경이 production 환경과 다른 위치에 precompile된 asset을 저장하도록 구성되어야 합니다. 그렇지 않으면, production에서 사용하기 위해 precompile된 모든 asset이 development에서의 요청을 덮어쓰게 됩니다(즉, asset에 대한 후속 변경사항이 브라우저에 반영되지 않습니다).
이를 위해
config/environments/development.rb
에 다음 라인을 추가할 수 있습니다:config.assets.prefix = "/dev-assets"
배포 도구(예: Capistrano)의 asset precompile 태스크는 비활성화되어야 합니다.
필요한 모든 compressor나 minifier가 개발 시스템에서 사용 가능해야 합니다.
또한 임시 파일에 저장되는 무작위로 생성된 secret_key_base
의 사용을 트리거하기 위해 ENV["SECRET_KEY_BASE_DUMMY"]
를 설정할 수 있습니다. 이는 production 시크릿에 대한 접근이 필요하지 않은 빌드 단계의 일부로 production용 asset을 precompile할 때 유용합니다.
$ SECRET_KEY_BASE_DUMMY=1 bundle exec rails assets:precompile
5.3 Live Compilation
어떤 상황에서는 live compilation을 사용하고 싶을 수 있습니다. 이 모드에서는 pipeline의 모든 asset 요청이 Sprockets에 의해 직접 처리됩니다.
이 옵션을 활성화하려면 다음과 같이 설정하세요:
config.assets.compile = true
첫 번째 요청에서 assets는 Assets Cache Store에 설명된 대로 컴파일되고 캐시되며, helper에서 사용되는 manifest 이름들은 SHA256 hash를 포함하도록 변경됩니다.
Sprockets는 또한 Cache-Control
HTTP 헤더를 max-age=31536000
으로 설정합니다. 이는 서버와 클라이언트 브라우저 사이의 모든 캐시에게 이 컨텐츠(제공된 파일)를 1년 동안 캐시할 수 있다는 신호를 보냅니다. 이는 서버로부터 이 asset에 대한 요청 수를 줄이는 효과가 있습니다. 해당 asset은 로컬 브라우저 캐시나 중간 캐시에 있을 가능성이 높습니다.
이 모드는 더 많은 메모리를 사용하고 기본값보다 성능이 떨어지므로 권장되지 않습니다.
5.4 CDN
CDN은 Content Delivery Network의 약자입니다. CDN은 브라우저가 asset을 요청할 때 지리적으로 가까운 곳에서 캐시된 복사본을 제공할 수 있도록 전 세계에 걸쳐 asset을 캐시하도록 설계되었습니다. production 환경에서 Rails 서버에서 직접 asset을 제공하는 경우, 애플리케이션 앞에 CDN을 사용하는 것이 가장 좋은 방법입니다.
CDN을 사용하는 일반적인 패턴은 production 애플리케이션을 "origin" 서버로 설정하는 것입니다. 이는 브라우저가 CDN에서 asset을 요청했을 때 캐시 미스가 발생하면, 즉시 서버에서 파일을 가져와 캐시한다는 의미입니다. 예를 들어 example.com
에서 Rails 애플리케이션을 실행하고 mycdnsubdomain.fictional-cdn.com
에 CDN을 구성한 경우, mycdnsubdomain.fictional-cdn.com/assets/smile.png
로 요청이 들어오면 CDN은 example.com/assets/smile.png
에서 한 번 서버를 조회하고 요청을 캐시합니다. 같은 URL로 CDN에 들어오는 다음 요청은 캐시된 복사본을 사용하게 됩니다. CDN이 asset을 직접 제공할 수 있을 때는 요청이 Rails 서버에 전혀 도달하지 않습니다. CDN의 asset은 브라우저와 지리적으로 더 가깝기 때문에 요청이 더 빠르며, 서버가 asset을 제공하는 데 시간을 쓸 필요가 없어 애플리케이션 코드를 최대한 빠르게 제공하는 데 집중할 수 있습니다.
5.4.1 정적 Asset을 제공하기 위한 CDN 설정
CDN을 설정하려면 애플리케이션이 production 환경에서 인터넷상의 공개적으로 접근 가능한 URL(예: example.com
)에서 실행되고 있어야 합니다. 그다음 클라우드 호스팅 제공업체의 CDN 서비스에 가입해야 합니다. 이때 CDN의 "origin"이 웹사이트 example.com
을 가리키도록 구성해야 합니다. origin 서버 구성에 대한 자세한 내용은 제공업체의 문서를 확인하세요.
프로비저닝한 CDN은 mycdnsubdomain.fictional-cdn.com
과 같이 애플리케이션을 위한 사용자 지정 서브도메인을 제공해야 합니다(참고: fictional-cdn.com은 이 글을 작성하는 시점에서 유효한 CDN 제공업체가 아닙니다). CDN 서버를 구성했다면, 브라우저가 Rails 서버에 직접 접근하는 대신 CDN을 사용하여 asset을 가져오도록 해야 합니다. Rails에서 상대 경로 대신 CDN을 asset host로 설정하면 이를 수행할 수 있습니다. Rails에서 asset host를 설정하려면 config/environments/production.rb
에서 [config.asset_host
][]를 설정해야 합니다:
config.asset_host = "mycdnsubdomain.fictional-cdn.com"
"host"만 제공하면 됩니다. 이는 서브도메인과 루트 도메인을 의미하며, http://
나 https://
와 같은 프로토콜이나 "scheme"을 지정할 필요는 없습니다. 웹 페이지가 요청될 때, asset 링크의 프로토콜은 기본적으로 웹 페이지에 접근하는 방식과 일치하게 됩니다.
또한 사이트의 staging 복사본을 더 쉽게 실행할 수 있도록 environment variable을 통해 이 값을 설정할 수 있습니다:
config.asset_host = ENV["CDN_HOST"]
위 기능이 작동하려면 서버의 CDN_HOST
를 mycdnsubdomain.fictional-cdn.com
으로 설정해야 합니다.
서버와 CDN을 구성한 후에는 다음과 같은 helper의 asset path가:
<%= asset_path('smile.png') %>
http://mycdnsubdomain.fictional-cdn.com/assets/smile.png
와 같은 완전한 CDN URL로 렌더링됩니다(가독성을 위해 digest는 생략됨).
CDN이 smile.png
의 복사본을 가지고 있다면, 브라우저에 직접 제공하며 서버는 요청된 사실조차 알 수 없습니다. CDN이 복사본을 가지고 있지 않다면, "origin" example.com/assets/smile.png
에서 파일을 찾으려 시도하고, 이후 사용을 위해 저장합니다.
일부 assets만 CDN에서 제공하고 싶다면, asset helper에서 커스텀 :host
옵션을 사용할 수 있습니다. 이는 [config.action_controller.asset_host
][]에 설정된 값을 덮어씁니다.
<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %>
5.4.2 CDN 캐싱 동작 커스터마이징
CDN은 콘텐츠를 캐싱하여 작동합니다. CDN에 오래되거나 잘못된 콘텐츠가 있다면, 이는 애플리케이션에 도움이 되기보다는 해가 됩니다. 이 섹션의 목적은 대부분의 CDN의 일반적인 캐싱 동작을 설명하는 것입니다. 특정 제공업체는 약간 다르게 동작할 수 있습니다.
5.4.2.1 CDN 요청 캐싱
CDN이 asset을 캐싱하는 데 좋다고 설명되지만, 실제로는 전체 요청을 캐싱합니다. 여기에는 asset의 본문뿐만 아니라 모든 헤더가 포함됩니다. 가장 중요한 것은 CDN(및 웹 브라우저)에게 콘텐츠를 어떻게 캐싱할지 알려주는 Cache-Control
헤더입니다. 이는 누군가 /assets/i-dont-exist.png
와 같이 존재하지 않는 asset을 요청하고, Rails 애플리케이션이 404를 반환할 경우, 유효한 Cache-Control
헤더가 있다면 CDN이 404 페이지를 캐싱할 가능성이 있다는 것을 의미합니다.
5.4.2.2 CDN 헤더 디버깅
CDN에서 헤더가 제대로 캐싱되었는지 확인하는 한 방법은 curl을 사용하는 것입니다. 서버와 CDN 모두에서 헤더를 요청하여 동일한지 확인할 수 있습니다:
$ curl -I http://www.example/assets/application-
d0e099e021c95eb0de3615fd1d8c4d83.css
HTTP/1.1 200 OK
Server: Cowboy
Date: Sun, 24 Aug 2014 20:27:50 GMT
Connection: keep-alive
Last-Modified: Thu, 08 May 2014 01:24:14 GMT
Content-Type: text/css
Cache-Control: public, max-age=2592000
Content-Length: 126560
Via: 1.1 vegur
CDN 복사본과 비교했을 때:
$ curl -I http://mycdnsubdomain.fictional-cdn.com/application-
d0e099e021c95eb0de3615fd1d8c4d83.css
HTTP/1.1 200 OK 서버: Cowboy 마지막
수정: 2014년 5월 8일 목요일 01:24:14 GMT Content-Type: text/css
Cache-Control:
public, max-age=2592000
Via: 1.1 vegur
Content-Length: 126560
Accept-Ranges:
bytes
날짜: 2014년 8월 24일 일요일 20:28:45 GMT
Via: 1.1 varnish
Age: 885814
Connection: keep-alive
X-Served-By: cache-dfw1828-DFW
X-Cache: HIT
X-Cache-Hits:
68
X-Timer: S1408912125.211638212,VS0,VE0
CDN 문서에서 X-Cache
또는 그들이 추가할 수 있는 추가 헤더들에 대한 추가 정보를 확인하세요.
5.4.2.3 CDN과 Cache-Control 헤더
Cache-Control
헤더는 요청이 어떻게 캐시될 수 있는지를 설명합니다. CDN이 사용되지 않을 때는 브라우저가 이 정보를 사용하여 콘텐츠를 캐시합니다. 이는 수정되지 않은 asset들에 매우 유용한데, 브라우저가 매 요청마다 웹사이트의 CSS나 JavaScript를 다시 다운로드할 필요가 없기 때문입니다. 일반적으로 우리는 Rails 서버가 CDN(과 브라우저)에게 asset이 "public"이라고 알리기를 원합니다. 이는 모든 캐시가 요청을 저장할 수 있다는 것을 의미합니다. 또한 일반적으로 max-age
를 설정하길 원하는데, 이는 캐시가 캐시를 무효화하기 전까지 객체를 저장할 시간입니다. max-age
값은 초 단위로 설정되며 최대 가능한 값은 1년인 31536000
입니다. Rails 애플리케이션에서는 다음과 같이 설정할 수 있습니다.
config.public_file_server.headers = {
"Cache-Control" => "public, max-age=31536000"
}
이제 애플리케이션이 프로덕션 환경에서 asset을 제공할 때, CDN은 최대 1년 동안 해당 asset을 저장할 것입니다. 대부분의 CDN이 요청의 헤더도 캐시하기 때문에, 이 Cache-Control
은 이 asset을 요청하는 모든 미래의 브라우저에 전달될 것입니다. 따라서 브라우저는 이 asset을 다시 요청하기 전에 매우 오랫동안 저장할 수 있다는 것을 알게 됩니다.
5.4.2.4 CDN과 URL 기반 캐시 무효화
대부분의 CDN은 전체 URL을 기반으로 asset의 내용을 캐시합니다. 이는 다음과 같은 요청을 의미합니다:
http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png
다른 완전히 다른 캐시가 됩니다
http://mycdnsubdomain.fictional-cdn.com/assets/smile.png
Cache-Control
에서 긴 시간의 max-age
를 설정하고 싶다면(그리고 그렇게 해야 합니다),
asset을 변경할 때 캐시가 무효화되도록 해야 합니다. 예를 들어 이미지의 스마일 얼굴을 노란색에서 파란색으로 변경할 때, 사이트의 모든 방문자가 새로운 파란색 얼굴을 보게 하고 싶을 것입니다. Rails asset pipeline과 함께 CDN을 사용할 때 config.assets.digest
는 기본적으로 true로 설정되어 있어서 asset이 변경될 때마다 다른 파일 이름을 갖게 됩니다. 이렇게 하면 캐시의 어떤 항목도 수동으로 무효화할 필요가 없습니다. 대신 다른 고유한 asset 이름을 사용함으로써, 사용자는 최신 asset을 받게 됩니다.
6 Pipeline 커스터마이징
6.1 CSS Compression
CSS를 압축하는 옵션 중 하나는 YUI입니다. YUI CSS compressor는 minification을 제공합니다.
다음 줄은 YUI 압축을 활성화하며, yui-compressor
gem이 필요합니다.
config.assets.css_compressor = :yui
CSS 압축기로 YUI를 사용합니다
6.2 JavaScript 압축
JavaScript 압축을 위한 가능한 옵션들은 :terser
, :closure
, :yui
입니다. 이들은 각각 terser
, closure-compiler
, yui-compressor
gem들이 필요합니다.
예를 들어 terser
gem을 보겠습니다.
이 gem은 Ruby에서 Terser(Node.js용으로 작성됨)를 래핑합니다. 공백과 주석을 제거하고, 로컬 변수명을 짧게 만들며, 가능한 경우 if
와 else
문을 삼항 연산자로 변경하는 등의 마이크로 최적화를 수행하여 코드를 압축합니다.
다음 라인은 JavaScript 압축을 위해 terser
를 호출합니다.
config.assets.js_compressor = :terser
terser
를 사용하기 위해서는 ExecJS가 지원하는 런타임이 필요합니다. macOS나 Windows를 사용하고 계시다면 운영체제에 JavaScript 런타임이 이미 설치되어 있습니다.
JavaScript 압축은 importmap-rails
나 jsbundling-rails
gem을 통해 asset을 로드할 때도 JavaScript 파일에 대해 작동합니다.
6.3 Asset GZip 압축하기
기본적으로 컴파일된 asset의 gzip 버전이 일반 버전과 함께 생성됩니다. Gzip 압축된 asset은 네트워크를 통한 데이터 전송을 줄이는데 도움을 줍니다. gzip
플래그를 설정하여 이를 구성할 수 있습니다.
config.assets.gzip = false # gzip 압축된 asset 생성 비활성화
웹서버의 gzip으로 압축된 asset을 서빙하는 방법에 대해서는 웹서버의 문서를 참고하세요.
6.4 자체 Compressor 사용하기
CSS와 JavaScript용 compressor 설정은 모든 객체를 받을 수 있습니다.
이 객체는 문자열을 유일한 인자로 받는 compress
메서드를 가지고 있어야 하며, 문자열을 반환해야 합니다.
class Transformer
def compress(string)
do_something_returning_a_string(string)
end
end
application.rb
의 config 옵션에 새로운 객체를 전달하여 이를 활성화할 수 있습니다:
config.assets.css_compressor = Transformer.new
6.5 assets 경로 변경하기
Sprockets가 기본적으로 사용하는 public 경로는 /assets
입니다.
이는 다른 것으로 변경할 수 있습니다:
config.assets.prefix = "/some_other_path"
assets가 서비스되는 경로를 변경합니다.
만약 asset pipeline을 사용하지 않는 오래된 프로젝트를 업데이트하거나 새로운 리소스에 대해 이 경로를 사용하고자 할 때 유용한 옵션입니다.
6.6 X-Sendfile 헤더
X-Sendfile 헤더는 애플리케이션의 응답을 무시하고 대신 디스크의 지정된 파일을 제공하도록 하는 웹 서버에 대한 지시어입니다. 이 옵션은 기본적으로 비활성화되어 있지만, 서버가 지원하는 경우 활성화할 수 있습니다. 활성화되면 파일 제공의 책임을 웹 서버에 전달하여 더 빠른 처리가 가능합니다. 이 기능의 사용 방법에 대해서는 send_file을 참조하세요.
Apache와 NGINX는 이 옵션을 지원하며, config/environments/production.rb
에서 활성화할 수 있습니다:
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # Apache용
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # NGINX용
기존 애플리케이션을 업그레이드하고 이 옵션을 사용하려는 경우, 이 구성 옵션을 production.rb
와 production 동작을 정의한 다른 환경에만 붙여넣도록 주의하세요(application.rb
에는 붙여넣지 마세요).
자세한 내용은 production 웹 서버의 문서를 참고하세요:
7 Assets Cache Store
기본적으로 Sprockets는 development와 production 환경에서 tmp/cache/assets
에 assets를 캐시합니다. 이는 다음과 같이 변경할 수 있습니다:
config.assets.configure do |env|
env.cache = ActiveSupport::Cache.lookup_store(:memory_store,
{ size: 32.megabytes })
end
이 코드는 memory cache store를 사용하여 32메가바이트의 크기로 Asset 캐시를 설정합니다.
assets cache store를 비활성화하려면:
config.assets.configure do |env|
env.cache = ActiveSupport::Cache.lookup_store(:null_store)
end
8 Gem에 Asset 추가하기
Asset은 gem 형태의 외부 소스로부터 가져올 수도 있습니다.
좋은 예로 jquery-rails
gem이 있습니다.
이 gem은 Rails::Engine
을 상속하는 engine 클래스를 포함합니다.
이를 통해 Rails는 이 gem의 디렉토리가 asset을 포함할 수 있다는 것을 인식하고,
이 engine의 app/assets
, lib/assets
, vendor/assets
디렉토리가 Sprockets의 검색 경로에 추가됩니다.
9 라이브러리나 Gem을 Pre-Processor로 만들기
Sprockets는 Processors, Transformers, Compressors, Exporters를 사용하여
Sprockets 기능을 확장합니다. 자세한 내용은
Extending Sprockets
를 참조하세요. 여기서는 text/css (.css
) 파일 끝에 주석을 추가하는 전처리기를 등록했습니다.
module AddComment
def self.call(input)
{ data: input[:data] + "/* Hello From my sprockets extension */" }
end
end
이제 입력 데이터를 수정하는 module이 있으므로, 이를 MIME type의 preprocessor로 등록할 차례입니다.
Sprockets.register_preprocessor "text/css", AddComment
대체 라이브러리
수년에 걸쳐 asset을 처리하기 위한 여러 가지 기본 접근 방식이 있었습니다. 웹이 발전하면서 더 많은 JavaScript 중심의 애플리케이션을 보게 되었습니다. Rails Doctrine에서는 The Menu Is Omakase를 믿기 때문에 기본 설정인 Sprockets with Import Maps에 집중했습니다.
다양한 JavaScript와 CSS 프레임워크/확장에 대해 모든 상황에 적합한 하나의 해결책은 없다는 것을 알고 있습니다. Rails 생태계에는 기본 설정으로는 충분하지 않은 경우에 활용할 수 있는 다른 번들링 라이브러리들이 있습니다.
9.1 jsbundling-rails
jsbundling-rails
는 Bun, esbuild, rollup.js, 또는 Webpack으로 JS를 번들링하는 importmap-rails
방식의 JavaScript 런타임 의존적 대안입니다.
이 gem은 development 환경에서 변경 사항을 감시하고 자동으로 출력을 생성하기 위한 빌드 태스크를 package.json
에 제공합니다. production 환경에서는 모든 패키지 의존성이 설치되고 모든 엔트리 포인트에 대한 JavaScript가 빌드되었는지 확인하기 위해 javascript:build
태스크를 assets:precompile
태스크에 자동으로 연결합니다.
importmap-rails
대신 사용해야 하는 경우는? JavaScript 코드가 트랜스파일링에 의존하는 경우, 즉 Babel, TypeScript 또는 React JSX 형식을 사용하는 경우 jsbundling-rails
가 올바른 선택입니다.
9.2 Webpacker/Shakapacker
Webpacker
는 Rails 5와 6의 기본 JavaScript 전처리기 및 번들러였습니다. 현재는 지원이 종료되었습니다. shakapacker
라는 후속 버전이 존재하지만, Rails 팀이나 프로젝트에서 관리하지는 않습니다.
이 목록의 다른 라이브러리들과 달리 webpacker
/shakapacker
는 Sprockets와 완전히 독립적이며 JavaScript와 CSS 파일 모두를 처리할 수 있었습니다.
jsbundling-rails
와 webpacker
/shakapacker
의 차이점을 이해하려면 Comparison with Webpacker 문서를 읽어보세요.
9.3 cssbundling-rails
cssbundling-rails
는 Tailwind CSS, Bootstrap, Bulma, PostCSS, 또는 Dart Sass를 사용하여 CSS를 번들링하고 처리한 다음, asset pipeline을 통해 CSS를 전달할 수 있게 해줍니다.
이는 jsbundling-rails
와 유사한 방식으로 작동하며, 개발 환경에서 스타일시트를 재생성하기 위해 yarn build:css --watch
프로세스와 함께 Node.js 의존성을 애플리케이션에 추가하고, 프로덕션 환경에서는 assets:precompile
태스크에 연결됩니다.
Sprockets와의 차이점은 무엇인가요? Sprockets 자체로는 Sass를 CSS로 트랜스파일할 수 없습니다. Node.js는 .sass
파일에서 .css
파일을 생성하는 데 필요합니다. .css
파일이 생성되면 Sprockets는 이를 클라이언트에게 전달할 수 있습니다.
참고: cssbundling-rails
는 CSS 처리를 위해 Node에 의존합니다. dartsass-rails
와 tailwindcss-rails
gem들은 Tailwind CSS와 Dart Sass의 독립 실행형 버전을 사용하므로 Node 의존성이 필요하지 않습니다. JavaScript 처리를 위해 importmap-rails
를 사용하고 CSS를 위해 dartsass-rails
또는 tailwindcss-rails
를 사용한다면 Node 의존성을 완전히 피할 수 있어 더 단순한 솔루션을 얻을 수 있습니다.
9.4 dartsass-rails
애플리케이션에서 Sass를 사용하고자 한다면, 레거시 sassc-rails
gem의 대체제로 dartsass-rails
가 제공됩니다. dartsass-rails
는 sassc-rails
에서 사용하던 2020년에 deprecated된 LibSass 대신 Dart Sass 구현체를 사용합니다.
sassc-rails
와 달리 새로운 gem은 Sprockets와 직접적으로 통합되어 있지 않습니다. 설치/마이그레이션 방법은 gem 홈페이지를 참조하세요.
널리 사용되던 sassc-rails
gem은 2019년 이후로 관리되지 않고 있습니다.
9.5 tailwindcss-rails
tailwindcss-rails
는 Tailwind CSS v3 프레임워크의 독립 실행형 실행 파일 버전을 위한 wrapper gem입니다. rails new
명령어에 --css tailwind
가 제공될 때 새로운 애플리케이션에서 사용됩니다. 개발 환경에서 Tailwind 출력을 자동으로 생성하기 위한 watch
프로세스를 제공합니다. 프로덕션 환경에서는 assets:precompile
태스크에 연결됩니다.