본문 바로가기
WIKI 기술 지식 베이스

Build System

원문 보기 위키 갱신

Backstage 빌드 시스템은 Backstage 프로젝트를 린트, 테스트, 개발, 마지막으로 릴리스하는 데 도움을 주는 빌드 및 개발 도구 모음입니다.

출처: 문서

본문

Backstage 빌드 시스템은 Backstage 프로젝트를 린트, 테스트, 개발, 마지막으로 릴리스하는 데 도움을 주는 빌드 및 개발 도구 모음입니다. 빌드 시스템의 목적은 Backstage와 잘 동작하는 즉시 사용 가능한(out-of-the-box) 솔루션을 제공하고, 자체 도구를 설정하는 데 시간을 쓰는 대신 앱 구축에 집중하게 하는 것입니다.

빌드 시스템 설정은 @backstage/cli의 일부이며, @backstage/create-app를 사용해 만드는 모든 프로젝트에 이미 포함되어 있습니다. 예를 들어 create-react-app과 함께 제공되는 도구인 react-scripts와 유사합니다. Backstage 빌드 시스템은 Webpack, Rollup, Jest, ESLint 같은 JavaScript 및 TypeScript 생태계의 기존 오픈 소스 도구에 크게 의존합니다.

설계 고려 사항 (Design Considerations)

Backstage 빌드 시스템의 설계를 이끈 핵심 신념과 제약이 몇 가지 있습니다. 첫 번째이자 가장 중요한 것은 개발 경험을 최우선으로 한다는 것입니다. 모서리를 자르거나 복잡성을 더해야 한다면 다른 영역에서 그렇게 하되, 편집기를 켜고 코드를 반복하는 경험은 항상 최대한 매끄러워야 합니다.

또한 다음을 포함한 여러 하드·소프트 요구 사항이 있습니다.

  • Monorepos - 빌드 시스템은 다중 패키지 워크스페이스를 지원해야 합니다.
  • Publishing - 개별 패키지를 빌드하고 게시할 수 있어야 합니다.
  • Scale - 과도한 대기 시간 없이 수백 개의 대형 패키지로 확장해야 합니다.
  • Reloads - 개발 흐름은 저장 시 빠른 핫 리로드를 지원해야 합니다.
  • Simple - 사용법은 단순해야 하고 구성은 최소로 유지되어야 합니다.
  • Universal - 웹 애플리케이션, 동형 패키지, Node.js 모두를 개발 대상으로 합니다.
  • Modern - 빌드 시스템은 현대적인 환경을 대상으로 합니다.
  • Editors - 대부분의 편집기에서 타입 검사와 린팅을 사용할 수 있어야 합니다.

빌드 시스템 설계 당시 이 요구 사항 집합은 react-scripts 같은 기존 도구가 지원하지 않던 것이었습니다. 모노레포, 게시, 편집기 지원이 결합된 확장 요구 사항 때문에 우리는 자체 특수 설정을 채택하게 되었습니다.

구조 (Structure)

Backstage 내 개발 흐름을 몇 가지 단계로 나눌 수 있습니다.

  • Formatting - 소스 코드에 일관된 형식을 적용합니다.
  • Linting - 잠재적 문제에 대해 소스 코드를 분석합니다.
  • Type Checking - TypeScript 타입이 유효한지 검증합니다.
  • Testing - 프로젝트에 대해 다양한 수준의 테스트 스위트를 실행합니다.
  • Building - 개별 패키지의 소스 코드를 컴파일합니다.
  • Bundling - 패키지와 모든 의존성을 프로덕션 준비 번들로 결합합니다.

이 단계들은 일반적으로 서로 격리되어 유지되며, 각 단계는 특정 작업에 집중합니다. 예를 들어 빌드나 번들링과 함께 린팅이나 타입 검사를 수행하지 않습니다. 이는 더 많은 유연성을 제공하고 중복 작업을 피해 성능을 향상시키기 위해서입니다. Backstage 내 개발의 일부로 포맷팅, 린팅, 타입 검사를 지원하는 코드 편집기나 IDE를 사용하는 것을 강력히 권장합니다.

이제 각 단계를 자세히 살펴보고 일반적인 Backstage 앱에서 어떻게 구현되는지 알아보겠습니다.

패키지 역할 (Package Roles)

패키지 역할은 2022년 3월에 도입되었습니다. 기존 프로젝트를 마이그레이션하려면 마이그레이션 가이드를 참조하세요.

Backstage 빌드 시스템은 구성을 간결하게 유지하고, 유틸리티와 도구를 제공하며, 최적화를 가능하게 하기 위해 패키지 역할 개념을 사용합니다. 패키지 역할은 패키지의 목적이 무엇인지 식별하는 단일 문자열이며, 각 패키지의 package.json에 다음과 같이 정의됩니다.

{  "name": "my-package",  "backstage": {    "role": "<role>"  },  ...}

Backstage 빌드 시스템이 현재 지원하는 사용 가능한 역할은 다음과 같습니다.

Role Description Example
frontend Bundled frontend application packages/app
backend Bundled backend application packages/backend
cli Package used as a command-line interface @backstage/cli
web-library Web library for use by other packages @backstage/plugin-catalog-react
node-library Node.js library for use by other packages @backstage/plugin-techdocs-node
common-library Isomorphic library for use by other packages @backstage/plugin-permission-common
frontend-plugin Backstage frontend plugin @backstage/plugin-scaffolder
frontend-plugin-module Backstage frontend plugin module @backstage/plugin-analytics-module-ga
backend-plugin Backstage backend plugin @backstage/plugin-auth-backend
backend-plugin-module Backstage backend plugin module @backstage/plugin-search-backend-module-pg

아래에서 다루는 대부분의 단계는 패키지 스크립트로 사용하기 위한 동반 명령이 있습니다. 명령은 모두 backstage-cli package 범주 아래에 있으며, 많은 명령이 패키지 역할에 따라 다르게 동작합니다. 명령은 다음과 같이 사용하기 위한 것입니다.

{  "scripts": {    "start": "backstage-cli package start",    "build": "backstage-cli package build",    "lint": "backstage-cli package lint",    ...  }}

포맷팅 (Formatting)

포맷팅 설정은 각 Backstage 애플리케이션 안에 완전히 있으며 CLI의 일부가 아닙니다. @backstage/create-app으로 만든 앱에서는 포맷팅을 prettier가 처리하지만, 각 애플리케이션은 자체 포맷팅 규칙을 선택하고 원한다면 다른 포매터로 전환할 수 있습니다.

린팅 (Linting)

Backstage CLI에는 eslint의 얇은 래퍼인 lint 명령이 포함되어 있습니다. 린트 파일 집합에 .ts 및 .tsx 확장자를 포함하는 것처럼 구성으로 설정할 수 없는 몇 가지 옵션을 추가합니다. lint 명령은 단지 합리적인 기본값을 제공하며 사용자 정의할 의도는 없습니다. 더 고급 옵션을 제공하려면 대신 eslint를 직접 호출하면 됩니다.

lint 명령 외에도 Backstage CLI에는 프런트엔드용 하나와 백엔드용 하나의 기본 ESLint 구성 집합이 포함되어 있습니다. 이러한 린트 구성은 차례로 @spotify/web-scripts의 린트 규칙 위에 구축됩니다.

표준 Backstage 설정에서 각 개별 패키지에는 자체 린트 구성이 있으며, 전체 프로젝트에 적용되는 루트 구성도 있습니다. 각 패키지의 구성은 패키지 역할에 따라 결정되는 표준 구성에서 시작하지만, 각 패키지의 요구에 맞게 사용자 정의할 수 있습니다.

최소한의 .eslintrc.js 구성은 이제 다음과 같습니다.

module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);

각 패키지에 대한 사용자 지정 재정의를 선택적 두 번째 인수로 제공할 수 있습니다.

module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {  ignorePatterns: ['templates/'],  rules: {    'jest/expect-expect': 'off',  },});

구성 팩토리는 또한 일반 ESLint로는 매우 번거로운 방식으로 구성을 확장하기 위한 유틸리티를 제공합니다. 특히 no-restricted-syntax 같은 규칙의 경우 그렇습니다. 사용 가능한 추가 키는 다음과 같습니다.

Key Description
tsRules TypeScript 파일에 적용할 추가 규칙
testRules 테스트 파일에 적용할 추가 규칙
restrictedImports no-restricted-imports에 추가할 경로
restrictedImportPatterns no-restricted-imports에 추가할 패턴
restrictedSrcImports src 파일의 no-restricted-imports에 추가할 경로
restrictedTestImports test 파일의 no-restricted-imports에 추가할 경로
restrictedSyntax no-restricted-syntax에 추가할 패턴
restrictedSrcSyntax src 파일의 no-restricted-syntax에 추가할 패턴
restrictedTestSyntax test 파일의 no-restricted-syntax에 추가할 패턴

타입 검사 (Type Checking)

포맷팅과 마찬가지로 Backstage CLI에는 타입 검사를 위한 자체 명령이 없습니다. 다만 권장 기본값과 빌드 시스템이 작동하기 위한 몇 가지 필수 설정을 모두 가진 기본 구성이 있습니다.

Backstage 프로젝트의 TypeScript 설정에서 아마 가장 주목할 만한 점은 전체 프로젝트가 하나의 큰 컴파일 단위라는 것입니다. 이는 성능 최적화와 사용 편의성 때문인데, 프로젝트를 더 작은 조각으로 나누면 설정이 더 복잡해질 뿐만 아니라 전체 프로젝트의 타입 검사가 한 자릿수 이상 느려지는 것으로 입증되었기 때문입니다. 이 설정이 작동하려면 각 패키지의 진입점이 TypeScript 소스 파일을 가리켜야 하며, 이는 게시 중에 몇 가지 복잡성을 야기하는데 그 섹션에서 다루겠습니다.

타입 검사는 일반적으로 로컬 개발을 위해 증분(incremental)으로 구성되며, 출력은 저장소 루트의 dist-types 폴더에 저장됩니다. 이는 로컬에서 tsc를 여러 번 실행할 때 상당한 속도 향상을 제공하지만, 초기 실행은 조금 느리게 만듭니다. 초기 실행이 느리기 때문에 모든 Backstage 앱에 기본적으로 포함되어 CI에서 사용하기 위한 tcs:full Yarn 스크립트에서는 증분 타입 검사를 비활성화합니다.

기본적으로 사용되는 또 다른 최적화는 라이브러리 타입 검사를 건너뛰는 것으로, 이는 TypeScript가 node_modules 내 타입이 건전한지 검증하지 않는다는 뜻입니다. 이 검사를 비활성화하면 타입 검사가 크게 빨라지지만, 궁극적으로 여전히 완전히 생략해서는 안 되는 중요한 검사이며, 로컬 개발 중에 도입된 문제를 잡을 가능성이 낮을 뿐입니다. 대신 우리는 tsc:full 스크립트를 통해 node_modules를 포함한 전체 타입 검사를 실행하는 검사를 CI에 포함하기로 선택합니다.

위에서 언급한 두 가지 이유로 CI에서 타입 검사를 실행할 때는 tsc:full 스크립트를 사용하는 것을 강력히 권장합니다.

테스트 (Testing)

위에서 언급했듯이 Backstage CLI는 테스트 실행과 단언을 모두 다루는 JavaScript 테스트 프레임워크인 Jest를 사용합니다. Jest는 프런트엔드 브라우저 코드를 포함한 모든 테스트를 Node.js에서 실행합니다. 사용하는 기법은 jsdom에 기반한 것 같은 다양한 사전 정의 환경을 사용해 Node.js VM에서 테스트를 실행하여 브라우저 API와 동작을 흉내 내는 것입니다.

Backstage CLI에는 테스트 실행을 돕는 자체 명령 backstage-cli test와 자체 구성 @backstage/cli/config/jest.js가 있습니다. 이 명령은 jest를 직접 실행하는 것의 비교적 얇은 래퍼입니다. 주요 책임은 포함된 구성이 사용되도록 하고 NODE_ENV 및 TZ 환경 변수를 설정하며, Git 저장소 내에서 실행할 때 --watch 같은 합리적인 기본 플래그를 제공하는 것입니다.

가장 많은 작업은 Backstage CLI에 포함된 Jest 구성이 수행합니다. 기본 Jest 구성을 제공할 뿐만 아니라 각 package.json에서 구성 재정의를 정의할 수 있게 해줍니다. 실제로 어떻게 할 수 있는지는 Jest 구성 섹션에서 논의합니다.

빌드 (Building)

빌드 프로세스의 주요 목적은 게시를 위해 패키지를 준비하는 것이지만, 백엔드 번들링 프로세스의 일부로도 사용됩니다. 이 두 경우에만 사용되므로 프로젝트의 백엔드 부분을 사용하지 않는 Backstage 앱은 빌드 프로세스와 전혀 상호작용할 필요가 없을 수 있습니다. 그렇지만 게시된 모든 Backstage 패키지가 이 프로세스로 빌드되므로 작동 방식을 아는 것이 유용할 수 있습니다.

빌드는 현재 Rollup을 사용하며 각 개별 패키지에 대해 격리되어 실행됩니다. 빌드는 package build 명령으로 호출되며, 번들링되는 역할인 frontend와 backend를 제외한 모든 패키지 역할에 적용됩니다.

빌드 프로세스에는 세 가지 가능한 출력이 있습니다: CommonJS 모듈 형식의 JavaScript, ECMAScript 모듈 형식의 JavaScript, 그리고 타입 선언입니다. 각 빌드 명령 호출은 이러한 출력 중 하나 이상을 패키지의 dist 폴더에 쓰고, 추가로 스타일시트나 이미지 같은 자산 파일을 복사합니다. 빌드 프로세스가 지원하는 구문과 파일 형식에 대한 자세한 내용은 loaders 섹션을 참조하세요.

CommonJS 또는 ESM 출력을 빌드할 때 빌드 명령은 항상 src/index.ts를 진입점으로 사용합니다. 모든 비상대(non-relative) 모듈 임포트는 외부로 간주되므로 Rollup 빌드는 패키지 자체의 소스 코드만 컴파일합니다. 같은 모노레포 내의 것까지 포함한 외부 의존성의 모든 임포트 문은 그대로 유지됩니다.

타입 정의 빌드는 아주 다르게 작동합니다. 타입 정의 빌드의 진입점은 프로젝트 루트의 dist-types 폴더 내 패키지의 상대 위치입니다. 이는 타입 정의가 있는 패키지를 빌드하기 전에 타입 검사를 실행하는 것이 중요하며, TypeScript 구성에서 타입 선언 방출이 활성화되어 있어야 함을 의미합니다. 타입 정의 빌드 단계의 이유는 패키지에서 내보낸 것만 제외한 모든 타입을 제거하여 훨씬 깔끔한 타입 정의 파일을 남기고, 타입 정의가 생성된 JavaScript와 동기화되도록 하는 것입니다.

모듈 페더레이션 리모트 빌드 (Building Module Federation Remotes)

프런트엔드 플러그인 패키지는 모듈 페더레이션 리모트로 빌드할 수 있으며, 이를 통해 모듈 페더레이션 호스트(일반적으로 기본 프런트엔드 앱)가 런타임에 동적으로 로드할 수 있습니다. 패키지를 모듈 페더레이션 리모트로 빌드하려면 package build 명령과 함께 --module-federation 옵션을 사용하세요.

자세한 내용은 Module Federation 문서에 나와 있습니다.

번들링 (Bundling)

번들링 프로세스의 목표는 여러 패키지를 단일 런타임 단위로 결합하는 것입니다. 수행 방식은 프런트엔드와 백엔드, 그리고 로컬 개발과 프로덕션 배포에 따라 다릅니다. 그 때문에 이러한 경우의 각 조합을 별도로 다룹니다.

프런트엔드 개발 (Frontend Development)

프런트엔드 개발 설정은 프런트엔드 역할을 가진 모든 패키지에 사용되며 package start 명령으로 호출됩니다. 서로 다른 역할의 유일한 차이는 'frontend' 역할의 패키지는 src/index를 진입점으로 사용하고, 다른 역할은 대신 dev/index를 사용한다는 것입니다. start 명령을 실행하면 구성의 app.baseUrl이 설정한 프로토콜, 호스트, 포트를 수신하는 개발 서버가 설정됩니다. 필요하다면 app.listen 구성을 통해 수신 옵션을 재정의할 수도 있습니다.

새 프런트엔드 시스템을 사용하는 프런트엔드 플러그인 패키지의 경우, dev/index 진입점을 설정하는 권장 방법은 @backstage/frontend-dev-utils의 createDevApp 헬퍼를 사용하는 것입니다. 이는 플러그인이 로드된 최소한의 Backstage 앱을 만들어 렌더링합니다.

in dev/index.ts

import { createDevApp } from '@backstage/frontend-dev-utils';import myPlugin from '../src';createDevApp({ features: [myPlugin] });

레거시 프런트엔드 시스템의 경우 @backstage/dev-utils 패키지가 동등한 헬퍼를 제공합니다.

프런트엔드 개발 번들링은 현재 Webpack과 Webpack Dev Server를 기반으로 합니다. Webpack 구성 자체는 프런트엔드 개발과 프로덕션 번들링 사이에 거의 차이가 없으므로 아래 프로덕션 섹션에서 구성에 대해 더 자세히 다루겠습니다. 주요 차이점은 process.env.NODE_ENV가 'development'로 설정되고, 축소(minification)가 비활성화되며, 저렴한 소스 맵이 사용되고, React Refresh가 활성화된다는 것입니다.

타입 검사와 린팅을 Webpack 프로세스의 일부로 실행하는 것을 선호한다면 --check 플래그를 전달해 ForkTsCheckerWebpackPlugin 사용을 활성화할 수 있습니다. 다만 위에서 언급했듯이 개발 중 이러한 검사를 처리하는 권장 방법은 내장 지원이 있는 편집기를 사용하는 것입니다.

프런트엔드 프로덕션 (Frontend Production)

프런트엔드 프로덕션 번들링은 전형적인 웹 콘텐츠 번들을 만들며, 모두 단일 폴더에 포함되어 정적 서빙에 사용할 준비가 됩니다. 'frontend' 역할의 패키지를 빌드할 때 사용되며, 개발 번들링과 달리 개별 플러그인의 프로덕션 번들을 빌드할 방법은 없습니다. 번들링 프로세스의 출력은 패키지의 dist 폴더에 기록됩니다.

개발 번들링과 마찬가지로 프로덕션 번들링은 Webpack을 기반으로 합니다. HtmlWebpackPlugin을 사용해 index.html 진입점을 생성하며, CLI에 포함된 기본 템플릿이 포함됩니다. 앱 패키지에 public/index.html을 추가하면 번들된 템플릿을 대체할 수 있습니다. 템플릿은 두 개의 전역 상수에 접근할 수 있습니다. 번들이 서빙되도록 의도된 공개 기본 경로인 publicPath와 @backstage/config의 일반 프런트엔드 범위 구성인 config입니다.

Webpack 구성에는 링크된 패키지에서 패키지를 올바르게 해석하는 사용자 지정 플러그인, react-dev-utils의 ModuleScopePlugin(임포트가 패키지 밖으로 나가지 않도록 함), 'buffer'와 'events' 같은 일부 Node.js 모듈에 대한 몇 가지 폴백, 프런트엔드 구성을 process.env.APP_CONFIG로 번들에 쓰는 플러그인, 그리고 마지막으로 esbuild-loader를 사용한 esbuild 축소가 포함됩니다. 물론 구성된 로더 집합도 있으며 loaders 및 transpilation 섹션에서 자세히 읽을 수 있습니다.

빌드 중에는 다음 상수도 설정됩니다.

process.env.NODE_ENV = 'production';process.env.BUILD_INFO = {  cliVersion: '0.4.0', // The version of the CLI package  gitVersion: 'v0.4.0-86-ge54815618', // output of `git describe --always`  packageVersion: '1.0.5', // The version of the app package itself  timestamp: 1678900000000, // Date.now() when the build started  commit: 'e548156182a973ed4b459e18533afc22c85ffff8', // output of `git rev-parse HEAD`};

번들링 프로세스의 출력은 별도의 캐싱 전략을 가진 두 가지 파일 범주로 나뉩니다. 첫 번째는 dist/ 폴더 루트에 평범한 이름을 가진 일반 자산 집합입니다. 이들은 수명이 짧은 캐싱 또는 캐싱 없이 서빙하고 싶을 것입니다. 두 번째는 dist/static/ 폴더의 해시된 정적 자산 집합으로, 훨씬 더 오래 캐시되도록 구성할 수 있습니다.

정적 자산의 구성은 빈번한 변경과 HTTP 2.0을 통한 서빙에 최적화되어 있습니다. 자산은 작은 청크로 적극적으로 분할되므로 브라우저가 이를 로드하기 위해 많은 작은 요청을 해야 합니다. 장점은 개별 플러그인과 패키지의 변경이 더 적은 수의 파일을 무효화하여 페이지 로드 성능에 큰 영향을 주지 않고 빠른 개발을 가능하게 한다는 것입니다.

백엔드 개발 (Backend Development)

백엔드 개발 설정은 번들링 프로세스를 사용하지 않습니다. TypeScript에서 JavaScript로의 즉석 변환(on-the-fly transpilation)과 함께 Node.js 프로세스를 직접 실행합니다. 변환은 SWC를 기반으로 한 사용자 지정 변환으로 수행됩니다.

개발 중 백엔드 Node.js 프로세스는 소스 코드에 변경이 있을 때마다 다시 시작됩니다. 이는 메모리 내 데이터가 손실된다는 뜻입니다. 다시 시작 사이에 데이터를 저장하기 위해 백엔드 프로세스에는 부모 CLI 프로세스에서 데이터를 저장하고 복원하는 IPC 채널이 있습니다. 이미 내장된 이 기능의 주요 목적은 개발 중 SQLite를 사용할 때 데이터베이스 내용을 복원하는 것입니다. @backstage/backend-dev-utils 패키지에서 내보낸 DevDataStore 유틸리티로 자체 목적에도 사용할 수 있습니다.

실행 중인 Node.js 프로세스를 검사하려면 --inspect 및 --inspect-brk 플래그를 사용할 수 있습니다. 이들은 node 실행에 옵션으로 전달됩니다.

백엔드 프로덕션 (Backend Production)

백엔드 프로덕션 번들링은 다른 번들링 옵션과 완전히 다른 설정을 사용합니다. Webpack을 사용하는 대신 백엔드 프로덕션 번들링은 백엔드 패키지와 그 모든 로컬 의존성을 배포 아카이브로 수집합니다. 아카이브는 dist/bundle.tar.gz에 기록되며 각 패키지의 패키징된 버전을 포함합니다. 아카이브의 패키지 레이아웃은 모노레포의 디렉터리 레이아웃과 동일하며, 번들에는 루트 package.json과 yarn.lock 파일도 포함됩니다.

프로덕션 번들을 만들기 전에 먼저 모든 백엔드 패키지를 빌드해야 합니다. backend:bundle 명령을 실행할 때 --build-dependencies 플래그를 전달하면 자동으로 수행할 수 있습니다. 빌드 프로세스 초기에 패키지가 이미 빌드되는 경우가 흔하고 다시 빌드하면 중복 작업이 생기므로 선택적 플래그입니다.

번들을 사용하려면 디렉터리에 추출하고 yarn install --production을 실행한 다음 백엔드 패키지를 Node.js 진입점으로 사용해 백엔드를 시작합니다. 예: node packages/backend.

dist/bundle.tar.gz에는 같은 레이아웃이지만 package.json 파일만 포함된 dist/skeleton.tar.gz가 함께 제공됩니다. 이 스켈레톤 아카이브는 Docker 이미지 빌드처럼 이로 인해 활성화되는 캐싱의 이점을 얻을 수 있는 환경에서 yarn install을 실행하는 데 사용할 수 있습니다. 스켈레톤 아카이브를 사용하려면 루트 package.json과 yarn.lock과 함께 대상 디렉터리로 복사하고, 아카이브를 추출한 다음 yarn install --production을 실행합니다. 그러면 대상 디렉터리에 모든 의존성이 설치되며, 그 위에 bundle.tar.gz 아카이브 내용을 복사하고 추출하는 즉시 백엔드가 실행 준비가 됩니다.

다음은 'backend' 역할의 패키지 빌드 출력을 이미지로 패키징하는 데 사용할 수 있는 Dockerfile 예시입니다.

FROM node:24-trixie-slimWORKDIR /appCOPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz# install sqlite3 dependenciesRUN apt-get update && \    apt-get install -y libsqlite3-dev python3 cmake g++ && \    rm -rf /var/lib/apt/lists/* && \    yarn config set python /usr/bin/python3RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./RUN tar xzf bundle.tar.gz && rm bundle.tar.gzCMD ["node", "packages/backend"]

변환 (Transpilation)

Backstage CLI가 사용하는 트랜스파일러는 위에서 언급한 것과 동일한 설계 고려 사항에 따라 선택되었습니다. 물론 몇 가지 특정 요구 사항은 TypeScript와 JSX 지원, 그리고 React 핫 리로드 또는 리프레시, Jest mock 호이스팅에 대한 지원입니다. Backstage CLI는 또한 최신 브라우저만 대상으로 하므로 실제로 변환 프로세스를 가능한 한 가볍게 유지하고 대부분의 구문을 그대로 두고 싶어합니다.

이러한 요구 사항 외에도 어떤 트랜스파일러를 사용할지 결정하는 요소는 속도입니다. 빌드 프로세스는 추가 플러그인 같은 것 없이 트랜스파일러와의 통합을 가볍게 유지합니다. 이를 통해 새 옵션과 최적화가 가능해지면 트랜스파일러를 교체하고 계속해서 사용 가능한 최상의 옵션을 사용할 수 있습니다.

현재 선택된 트랜스파일러는 esbuild와 Sucrase입니다. 두 트랜스파일러를 사용하는 이유는 esbuild가 Sucrase보다 빠르고 약간 더 나은 출력을 생성하지만 동일한 기능 집합을 가지지 않기 때문입니다. 예를 들어 React 핫 리로딩을 지원하지 않습니다.

다양한 옵션의 벤치마킹은 ts-build-bench에서 수행되었습니다. 이 벤치마킹 프로젝트는 다양한 모양과 크기의 모노레포 설정을 허용하지만, 우리 경우 가장 중요하게 고려하는 설정은 Webpack으로 번들링되는 많은 중간~대형 패키지를 가진 대형 모노레포입니다. 대략적인 결과로는 esbuild가 현재 가장 빠른 옵션이고, Sucrase가 그 뒤를 바짝 따르며, SWC가 그 뒤를 이었습니다. 그 뒤에는 변환 전용 모드로 실행되는 TypeScript 컴파일러까지 꽤 큰 격차가 있고, 마지막으로 테스트한 트랜스파일러 중 단연 가장 느린 Babel까지 또 한 번 점프가 있습니다.

이 벤치마크에 대해 주의할 점은 전체 Webpack 번들링 시간을 고려한다는 것입니다. 즉 일부 변환 옵션이 다른 것보다 몇 자릿수 빠를 수 있지만, 번들링 프로세스에는 들어가는 다른 것들이 많으므로 총 시간은 같은 방식으로 영향을 받지 않습니다. 그래도 예를 들어 Babel에서 Sucrase로 전환하면 번들링이 2배에서 5배까지 빨라질 수 있습니다.

로더 (Loaders)

Backstage CLI는 번들링, 테스트, 빌드, 타입 검사 등 빌드 시스템의 모든 부분에서 로더 집합을 지원하도록 구성되어 있습니다. 로더는 항상 파일 확장자를 기준으로 선택됩니다. 지원되는 모든 파일 확장자 목록은 다음과 같습니다.

Extension Exports Purpose
.ts Script Module TypeScript
.tsx Script Module TypeScript and XML
.mts Script Module ECMAScript Module TypeScript
.cts Script Module CommonJS TypeScript
.js Script Module JavaScript
.jsx Script Module JavaScript and XML
.mjs Script Module ECMAScript Module
.cjs Script Module CommonJS Module
.json JSON Data JSON Data
.yml JSON Data YAML Data
.yaml JSON Data YAML Data
.css classes Style sheet
.eot URL Path Font
.ttf URL Path Font
.woff2 URL Path Font
.woff URL Path Font
.bmp URL Path Image
.gif URL Path Image
.jpeg URL Path Image
.jpg URL Path Image
.png URL Path Image
.svg URL Path Image
.md URL Path Markdown File

ECMAScript 모듈 (ECMAScript Modules)

Backstage 도구는 Node.js 패키지에서 ECMAScript 모듈(ESM)을 지원합니다. 여기에는 로컬 개발, 빌드된 패키지, 테스트, 타입 검사 중 위에 나열된 모든 스크립트 모듈 파일 확장자 지원이 포함됩니다. 동적 임포트는 CommonJS에서 ESM 전용 패키지를 로드하는 데 사용할 수 있으며 그 반대도 마찬가지입니다. 다만 알아야 할 몇 가지 제한 사항이 있습니다.

  • 테스트에서 네이티브 ESM 지원을 활성화하려면 일반적으로 NODE_OPTIONS='--experimental-vm-modules'를 통해 --experimental-vm-modules 플래그를 활성화한 상태로 테스트를 실행해야 합니다.
  • package.json에서 패키지를 "type": "module"로 선언하는 것은 지원되지만, 테스트에서는 "type": "module" 선언 여부와 관계없이 모든 로컬 전이적 의존성도 ESM으로 처리됩니다.
  • 커버리지를 활성화한 상태로 테스트를 실행하면 기본 babel 커버리지 공급자가 명명된 내보내기(named exports)의 호이스팅을 방해할 수 있습니다. Jest 구성에서 "coverageProvider": "v8"를 설정해 v8 공급자를 대신 사용하면 해결할 수 있지만, v8 공급자는 babel 공급자보다 상당히 느립니다.
  • Node.js에는 CommonJS와의 ESM 상호 운용 계층이 있어 ESM에서 CommonJS 패키지의 명명된 내보내기를 식별할 수 있습니다. 이 상호 운용 계층은 .cts 또는 .cjs 확장자를 가진 패키지를 가져올 때만 활성화됩니다. 이는 상호 운용 계층이 NPM 생태계와 완전히 호환되지 않아 .js 파일에 대해 활성화하면 패키지가 깨지기 때문입니다.
  • CommonJS 패키지의 동적 임포트는 런타임(테스트 vs 로컬 개발 등)에 따라 모양이 달라집니다. 따라서 CommonJS 패키지의 동적 임포트를 피하고 대신 require를 사용하거나 위에서 언급한 명시적 CommonJS 확장자를 사용하는 것이 좋습니다. CommonJS 패키지를 동적으로 임포트해야 한다면 default 내보내기 사용을 피하세요. 그 모양이 환경마다 달라지고 모듈 객체의 모양에 따라 임포트를 수동으로 풀어야 하기 때문입니다.

Jest 구성 (Jest Configuration)

Backstage CLI는 자체 Jest 구성 파일을 번들하며, backstage-cli test 실행 시 자동으로 사용됩니다. @backstage/cli/config/jest.js에서 사용할 수 있으며 여기에서 검사할 수 있습니다. 이 구성의 사용은 backstage-cli test에 --config <path> 플래그를 전달하거나 패키지에 jest.config.js 또는 jest.config.ts 파일을 배치하여 재정의할 수 있습니다.

내장 구성은 몇 가지 이점과 기능을 제공합니다. 가장 중요한 것은 테스트 내에서 나열된 로더 지원을 가능하게 하는 기준 트랜스포머 및 모듈 구성입니다. 또한 src/setupTests.ts가 존재하면 자동으로 감지하고 사용하며, 선택한 트랜스파일러와 잘 작동하는 커버리지 구성을 제공합니다. 구성은 각 패키지 역할에 적절한 Jest 환경도 감지합니다. web-libraries는 "jsdom" 환경으로, node-libraries는 "node"로 실행하는 식입니다.

구성도 프로젝트 전반의 접근 방식을 취하며, 모노레포 내의 대부분(전부는 아니더라도) 패키지가 동일한 기본 구성을 사용할 것을 기대합니다. 이를 통해 모노레포의 모든 패키지에서 Jest 변환 캐시를 공유하고 불필요한 변환을 피하는 최적화가 가능해집니다. 또한 모든 Jest 구성을 한 번에 로드하고, 이를 통해 테스트가 있는 패키지로 작업 디렉터리를 설정할 필요 없이 모노레포 루트에서 yarn test <pattern>을 실행할 수 있게 합니다.

커버리지 임계값 설정이나 특정 변환 지원 같은 소규모 사용자 정의가 필요할 때는 package.json의 "jest" 필드를 통해 Jest 구성을 재정의할 수 있습니다. 전체 옵션 목록은 Jest 문서를 참조하세요. 이러한 재정의는 디렉터리 조상의 모든 package.json 파일에서 로드되므로 모노레포 루트의 package.json에 공통 구성을 배치할 수 있습니다. 여러 재정의가 발견되면 병합되며, 디렉터리 트리 아래쪽의 구성이 우선합니다.

단일 package.json의 재정의는 예를 들어 다음과 같을 수 있습니다.

"jest": {    "coverageThreshold": {      "global": {        "functions": 100,        "lines": 100,        "statements": 100      }    }  },

추가 구성 옵션 (Additional Configuration Options)

내장 @backstage/cli/config/jest 구성을 사용할 때 표준 Jest 옵션 외에 다음 옵션을 사용할 수 있습니다.

rejectFrontendNetworkRequests [boolean]

기본값: false

true로 설정하면 프런트엔드 패키지 테스트에서 네트워크 요청을 하려는 모든 시도가 오류가 됩니다. 이 옵션은 루트 package.json에서만 설정할 수 있으며 모노레포의 모든 프런트엔드 패키지에 적용됩니다.

예시 - 루트 package.json에서

"jest": {    "rejectFrontendNetworkRequests": true  },

캐싱 (Caching)

캐싱은 Backstage 빌드 시스템 전반에서 아껴서 사용됩니다. 항상 속도를 유지하기 위한 요구 사항이 아니라 약간의 추가 성능을 짜내는 방법으로 사용됩니다. 선택적 캐싱을 사용할 수 있는 위치 목록은 다음과 같습니다.

  • TypeScript - Backstage 프로젝트가 사용하는 기본 tsconfig.json은 incremental이 true로 설정되어 있어 타입 검사 결과의 로컬 캐싱을 활성화합니다. 다만 일반적으로 CI에서는 권장되지 않으며, --incremental false를 설정하는 yarn tsc:full이 선호됩니다.
  • Testing - backstage-cli repo test 명령에는 성공적인 테스트 결과 캐싱을 활성화하는 --success-cache 플래그가 있습니다. 이는 패키지 수준에서 수행되며, 패키지가 마지막 테스트 실행 이후 변경되지 않았고 성공했다면 테스트를 건너뜁니다. CI에서 사용이 권장되지만 로컬 개발 중에는 권장되지 않습니다.
  • Linting - backstage-cli repo lint 명령에는 성공적인 린팅 결과 캐싱을 활성화하는 --success-cache 플래그가 있습니다. 이는 패키지 수준에서 수행되며, 패키지가 마지막 린트 실행 이후 변경되지 않았고 성공했다면 린팅을 건너뜁니다. CI에서 사용이 권장되지만 로컬 개발 중에는 권장되지 않습니다.
  • Webpack - BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE 환경 변수를 사용해 프런트엔드 패키지 빌드의 실험적 캐싱을 활성화할 수 있습니다. 이는 Webpack 파일시스템 캐시를 활성화합니다.

Jest 테스트 디버깅 (Debugging Jest Tests)

단위 테스트로 작업할 때의 생산성을 위해 IDE에 디버깅을 구성하는 것이 매우 중요합니다. 문제의 근본 원인을 더 빨리 식별하는 데 도움이 됩니다.

모듈 변환과 같은 jest 런타임이 만족하도록 배치해야 하는 몇 가지 사항이 있어 순수 jest로 테스트를 실행할 수 없습니다. 따라서 그러한 것들을 배치하는 방법을 아는 Backstage CLI에 jest 실행 래핑을 위임합니다. 이는 IDE 내 테스트 실행이 CI 및 수동 명령줄 테스트 실행과 동일한 방식으로 작동하도록 정렬해 줍니다.

이를 염두에 두고 jest 역할의 backstage-cli로 backstage 구성 요소의 jest 테스트를 실행하기 위한 몇 가지 IDE 구성이 있습니다.

IntelliJ IDEA

Jest 구성 템플릿을 다음으로 업데이트하세요.

  • 상단 패널에서 "Edit Configurations"를 클릭합니다.
  • 모달 대화 상자에서 왼쪽 하단에 있는 "Edit configuration templates..." 링크를 클릭합니다.
  • "Configuration file": 비워 둡니다 (backstage-cli가 구성을 추가함).
  • "Node options": --experimental-vm-modules
  • "Jest package": ~/workspace/backstage/node_modules/@backstage/cli - backstage cli 패키지의 위치.
  • "Working directory": ~/workspace/backstage
  • "Jest Options": repo test --runInBand --watch=false

현재 intellij에는 jest 테스트를 마우스 오른쪽 클릭하고 "run"을 누르면 intellij가 jest 구성 대신 playwright 실행 구성을 만들려 한다는 문제가 있습니다. WEB-67720을 참조하세요.

intellij 관리자가 문제를 해결할 때까지 jest 구성을 수동으로 만드세요. 다행히 intellij는 템플릿에서 구성을 미리 채워 줍니다. 해야 할 일은 테스트 파일 경로를 제공하는 것뿐입니다. intellij가 파일에서 테스트를 실행한 후에는 실행 패널에서 개별 테스트를 클릭하고 다시 실행할 수 있으며, 이번에는 intellij가 올바른 jest 실행 구성을 만듭니다.

VS Code

  • VS Code용 Jest 확장을 설치합니다.
  • .vscode 폴더의 settings.json을 다음으로 업데이트합니다.
{  "jest.jestCommandLine": "yarn test",  // In a large repo like the Backstage main repo you likely want to disable  // watch mode and the initial test run too, leaving just manual and perhaps  // on-save test runs in place.  "jest.runMode": "on-save"}
  • .vscode 폴더의 launch.json에 VS Code용 실행 구성을 추가합니다. 디버깅을 위한 완전한 구성은 다음과 같을 수 있습니다.
{  "type": "node",  "name": "vscode-jest-tests.v2",  "request": "launch",  "args": [    "repo",    "test",    "--runInBand",    "--watchAll=false",    "--testNamePattern",    "${jest.testNamePattern}",    "--runTestsByPath",    "${jest.testFile}"  ],  "console": "integratedTerminal",  "internalConsoleOptions": "neverOpen",  "program": "${workspaceFolder}/node_modules/@backstage/cli/bin/backstage-cli"}
  • 이 구성은 "Run and Debug" 뷰에서 수동 실행을 위한 것이 아닙니다. 대신 Jest 테스트 탐색기나 테스트의 거터 메뉴를 사용하세요.

게시 (Publishing)

패키지 게시는 Backstage 빌드 시스템의 선택적 부분이며, 레지스트리에 패키지를 게시하지 않는 한 걱정할 필요가 없는 것입니다. 이 섹션의 문서 외에도 게시된 패키지의 메타데이터 섹션을 꼭 읽으세요.

패키지를 게시하려면 먼저 빌드해야 하며, 그러면 dist 폴더가 채워집니다. Backstage 빌드 시스템이 로컬 개발과 특정 TypeScript 및 번들링 설정에 최적화되어 있기 때문에 이 시점에서 즉시 패키지를 게시할 수는 없습니다. 이는 패키지의 진입점이 여전히 src/index.ts를 가리키지만, 게시된 패키지에서는 dist/를 가리키길 원하기 때문입니다.

이를 해결하기 위해 Backstage CLI는 게시를 위해 패키지를 준비하는 데 도움을 주는 prepack 및 postpack 명령을 제공합니다. 이 스크립트는 패키지 게시 전에 Yarn이 자동으로 실행합니다.

prepack 명령은 "publishConfig"의 진입점 필드(예: "main" 및 "module")를 가져와 package.json의 최상위 수준으로 이동합니다. 이를 통해 게시 중에 dist 폴더의 원하는 파일을 가리킬 수 있습니다. postpack 명령은 프로젝트를 깨끗하게 유지하기 위해 이 변경을 되돌립니다.

다음은 동형 라이브러리 패키지의 일반적인 설정 발췌입니다.

"main": "src/index.ts",  "types": "src/index.ts",  "publishConfig": {    "access": "public",    "main": "dist/index.cjs.js",    "module": "dist/index.esm.js",    "types": "dist/index.d.ts"  },  "scripts": {    "build": "backstage-cli package build",    "lint": "backstage-cli package lint",    "test": "backstage-cli package test",    "clean": "backstage-cli package clean",    "prepack": "backstage-cli package prepack",    "postpack": "backstage-cli package postpack"  },  "files": ["dist"],

하위 경로 내보내기 (Subpath Exports)

Backstage CLI는 package.json의 "exports" 필드를 통해 하위 경로 내보내기 구현을 지원합니다. 예를 들어 다음과 같을 수 있습니다.

"name": "@backstage/plugin-foo",  "exports": {    ".": "./src/index.ts",    "./components": "./src/components.ts",  },

이를 통해 @backstage/plugins-foo로 src/index.ts에 내보낸 모든 것을, @backstage/plugins-foo/components로 src/components.ts를 가져올 수 있습니다. 패턴은 지원되지 않으므로 내보내기에 * 와일드카드가 포함될 수 없다는 점에 유의하세요.

Backstage CLI 빌드 시스템의 나머지와 마찬가지로 이 설정은 로컬 개발에 최적화되어 있어 "exports" 대상이 소스 파일을 직접 가리킵니다. package build 명령은 "exports" 필드를 감지하고 해당 dist 파일을 자동으로 생성하며, prepublish 명령은 "exports" 필드를 dist 파일을 가리키도록 다시 쓰고 이전 버전과의 호환성을 위해 폴더 기반 진입점도 생성합니다.

TypeScript 지원은 현재 typesVersions 필드를 통해 처리됩니다. 아직 "exports"와 잘 작동하는 모듈 해석 모드가 없기 때문입니다. typesVersions를 직접 만들 수도 있지만, migrate package-exports 명령으로 자동 생성될 수도 있습니다.

기존 패키지에 하위 경로 내보내기를 추가하려면 원하는 "exports" 필드를 추가한 다음 다음 명령을 실행하면 됩니다.

yarn backstage-cli migrate package-exports

더 알아보기 (Learn more)