v17부터 제공되는 새로운 빌드 시스템을 사용하면 Angular 애플리케이션을 더 효율적으로 빌드할 수 있습니다. 새 빌드 시스템에는 다음 기능이 포함됩니다:
- ESM을 사용하는 최신 빌드 형식과 모듈 지연 로딩을 지원하는 동적 import 표현식
- 최초 빌드와 증분 재빌드의 성능 개선
- esbuild, Vite 같은 새로운 JavaScript 생태계 도구
- 통합된 SSR 및 사전 렌더링 기능
- 전역 스타일시트와 컴포넌트 스타일시트의 자동 핫 교체
새 빌드 시스템은 안정적이며 Angular 애플리케이션에서 사용할 수 있도록 완전히 지원됩니다.
browser 빌더를 사용하는 애플리케이션은 새 빌드 시스템으로 전환할 수 있습니다.
커스텀 빌더를 사용한다면 해당 빌더의 문서에서 가능한 전환 방법을 확인하세요.
중요: 기존 webpack 기반 빌드 시스템과 browser 빌더는 사용 중단을 권고합니다.
일시적으로 browser 빌더를 계속 사용하거나 업데이트 중 전환하지 않도록 선택할 수 있지만 Angular 팀은 새 빌드 시스템으로 전환할 것을 권장합니다.
새 애플리케이션
새 애플리케이션은 기본적으로 application 빌더를 통해 새 빌드 시스템을 사용합니다.
기존 애플리케이션
프로젝트의 요구사항에 따라 자동 전환이나 수동 전환을 사용할 수 있습니다. v18부터는 새 메이저 버전으로 업데이트할 때 기존 애플리케이션을 새 빌드 시스템으로 자동 전환할지 묻습니다. 전환하기 전에 프로젝트와 관련된 내용이 있는지 알려진 문제 섹션을 확인하는 것이 좋습니다.
도움말: SSR을 사용한다면 애플리케이션 서버 코드에서 require, __filename, __dirname을 비롯해 CommonJS 모듈 스코프를 전제로 사용하는 코드를 제거하세요.
애플리케이션 코드는 모두 ESM과 호환되어야 합니다.
이 요구사항은 서드 파티 의존성에는 적용되지 않습니다.
자동 전환(권장)
자동 전환은 angular.json의 애플리케이션 설정과 코드, 스타일시트를 조정해서 기존 webpack 전용 기능의 사용을 제거합니다.
많은 변경사항을 자동으로 처리할 수 있고 대부분의 애플리케이션은 추가 수정이 필요하지 않지만, 애플리케이션마다 상황이 다르므로 일부는 직접 수정해야 할 수도 있습니다.
전환한 뒤에는 애플리케이션을 빌드해 보세요.
코드를 조정해야 하는 새로운 오류가 발생할 수도 있습니다.
오류 메시지는 가능한 경우 해결 방법을 제시하며, 이 가이드의 뒷부분에서도 자주 발생하는 상황을 설명합니다.
ng update로 새 메이저 버전으로 업데이트하면 이 전환을 실행할지 묻습니다.
전환은 전적으로 선택사항이며, 업데이트한 뒤 언제든 다음 명령으로 직접 실행할 수도 있습니다:
ng update @angular/cli --name use-application-builder
자동 전환은 다음 작업을 수행합니다:
- 기존
browser또는browser-esbuild대상을application으로 전환합니다. - 기존 SSR 빌더를 제거합니다.
이제
application이 이 작업을 처리합니다. - 변경사항에 맞춰 설정을 갱신합니다.
tsconfig.server.json을tsconfig.app.json과 병합하고,expressimport가 ESM을 준수하도록 TypeScript 옵션"esModuleInterop": true를 추가합니다.- 새로운 부트스트랩 방식과 빌드 결과물 디렉터리 구조를 사용하도록 애플리케이션 서버 코드를 갱신합니다.
@import/url()의 물결표나 캐럿 등 webpack 빌더 전용 스타일시트 구문을 제거하고, 같은 동작을 제공하도록 설정을 갱신합니다.@angular-devkit/build-angular를 사용하는 다른 곳이 없다면, 의존성이 더 적은 새로운 Node.js 패키지@angular/build를 사용하도록 전환합니다.
수동 전환
기존 프로젝트에서는 두 가지 방법 중 하나를 선택해 애플리케이션별로 새 빌더를 직접 활성화할 수도 있습니다. 두 방법 모두 안정적이며 Angular 팀에서 완전히 지원합니다. 어떤 방법을 사용할지는 전환에 필요한 변경사항의 양과 프로젝트에서 사용하려는 새 기능에 따라 결정하면 됩니다.
browser-esbuild빌더는 애플리케이션의 클라이언트 사이드 번들만 빌드하며, 기존 빌드 시스템을 제공하는browser빌더와 호환되도록 설계되었습니다. 같은 기능의 빌드 옵션을 제공하므로, 기존browser애플리케이션의 빌더를 그대로 교체해서 사용할 수 있는 경우가 많습니다.application빌더는 클라이언트 사이드 번들을 포함해 애플리케이션 전체를 처리합니다. 선택적으로 서버 사이드 렌더링을 위한 서버를 빌드하거나, 빌드 시점에 정적 페이지를 사전 렌더링할 수도 있습니다.
일반적으로는 application 빌더를 권장합니다.
서버 사이드 렌더링(SSR) 빌드를 개선하고, 클라이언트 사이드 렌더링 프로젝트에서도 나중에 SSR을 도입하기 쉬워지기 때문입니다.
다만 직접 전환하려면 작업이 조금 더 필요하며, 특히 기존 SSR 애플리케이션은 추가 작업이 필요합니다.
프로젝트에 application 빌더를 도입하기 어렵다면 browser-esbuild가 더 간단한 해결책이 될 수 있습니다.
호환성이 깨지는 변경사항을 줄이면서 빌드 성능 개선의 이점을 대부분 얻을 수 있습니다.
호환 빌더로 직접 전환하기
browser 빌더를 사용하는 애플리케이션이 이미 의존하는 @angular-devkit/build-angular 패키지는 browser-esbuild라는 빌더를 제공합니다.
browser 빌더를 사용하는 애플리케이션에서 새 빌드 시스템을 사용해 볼 수 있습니다.
커스텀 빌더를 사용한다면 해당 빌더의 문서에서 가능한 전환 방법을 확인하세요.
호환 옵션은 애플리케이션을 처음 전환할 때 필요한 변경사항을 최소화하도록 구현되었습니다.
이 옵션은 대체 빌더인 browser-esbuild를 통해 제공됩니다.
애플리케이션의 build 대상을 갱신해서 새 빌드 시스템으로 전환할 수 있습니다.
애플리케이션의 angular.json에는 일반적으로 다음과 같은 설정이 있습니다:
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
...
builder 필드만 변경하면 됩니다.
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser-esbuild",
...
새 application 빌더로 직접 전환하기
@angular-devkit/build-angular 패키지는 application이라는 빌더도 제공합니다.
ng new로 생성하는 새 애플리케이션은 @angular/build 패키지에서 제공하는 동일한 빌더(@angular/build:application)를 사용합니다.
애플리케이션의 angular.json에는 일반적으로 다음과 같은 설정이 있습니다:
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
...
먼저 builder 필드를 변경해야 합니다.
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
...
빌더 이름을 변경한 다음에는 build 대상의 옵션을 갱신해야 합니다.
조정해야 하는 browser 빌더 옵션은 다음과 같습니다:
main의 이름을browser로 변경합니다.polyfills는 파일 하나가 아니라 배열로 지정합니다.buildOptimizer는 제거합니다. 이 기능은optimization옵션이 처리합니다.resourcesOutputPath는 제거합니다. 이제 항상media를 사용합니다.vendorChunk는 제거합니다. 더 이상 필요하지 않은 성능 최적화 옵션입니다.commonChunk는 제거합니다. 더 이상 필요하지 않은 성능 최적화 옵션입니다.ngswConfigPath의 이름을serviceWorker로 변경합니다.
현재 SSR을 사용하지 않는 애플리케이션이라면 여기까지 수정한 뒤 ng build를 실행할 수 있습니다.
처음 ng build를 실행하면 동작 방식의 차이나 애플리케이션에서 사용하는 webpack 전용 기능 때문에 새로운 경고나 오류가 발생할 수 있습니다.
많은 경고 메시지에서 문제를 해결하는 방법을 안내합니다.
경고가 잘못된 것으로 보이거나 해결 방법이 명확하지 않다면 GitHub에 이슈를 등록해 주세요.
이 가이드의 뒷부분에서도 몇 가지 구체적인 상황과 현재 알려진 문제를 설명합니다.
SSR을 새로 도입하는 애플리케이션이라면 Angular SSR 가이드에서 SSR을 추가하는 설정 과정을 확인할 수 있습니다.
이미 SSR을 사용하는 애플리케이션은 새로운 통합 SSR 기능을 지원하도록 애플리케이션 서버를 추가로 조정해야 합니다.
이제 application 빌더는 다음 기존 빌더의 기능을 모두 통합해서 제공합니다:
app-shellprerenderserverssr-dev-server
ng update 과정은 일부 빌더가 이전에 포함되어 있던 @nguniversal 스코프 패키지의 사용을 자동으로 제거합니다.
새로운 @angular/ssr 패키지도 자동으로 추가해서 사용하며, 업데이트 과정에서 설정과 코드를 조정합니다.
@angular/ssr 패키지는 browser 빌더와 application 빌더를 모두 지원합니다.
빌드 실행하기
애플리케이션 설정을 갱신했다면 이전과 마찬가지로 ng build로 빌드할 수 있습니다.
전환할 때 선택한 빌더에 따라 일부 명령줄 옵션은 달라질 수 있습니다.
npm 스크립트나 다른 스크립트에 빌드 명령이 포함되어 있다면 확인해서 갱신하세요.
application 빌더로 전환한 애플리케이션에서 SSR이나 사전 렌더링을 사용한다면, 이제 ng build에 SSR 지원이 통합되었으므로 스크립트의 추가 ng run 명령을 제거할 수 있을지도 모릅니다.
ng build
개발 서버 실행하기
개발 서버는 새 빌드 시스템을 자동으로 감지하고 이를 사용해서 애플리케이션을 빌드합니다.
개발 서버를 실행하기 위해 dev-server 빌더 설정이나 명령줄을 변경할 필요는 없습니다.
ng serve
개발 서버에서는 이전에 사용하던 명령줄 옵션을 계속 사용할 수 있습니다.
도움말: 개발 서버를 실행하면 서버를 초기화하는 동안 잠시 스타일이 적용되지 않은 콘텐츠가 표시되는 현상(Flash of Unstyled Content, FOUC)이 발생할 수 있습니다. 개발 서버는 재빌드 시간을 줄이기 위해 스타일시트 처리를 처음 사용할 때까지 미루려고 합니다. 개발 서버 이외의 빌드에서는 이 현상이 발생하지 않습니다.
핫 모듈 교체
핫 모듈 교체(Hot Module Replacement, HMR)는 애플리케이션 일부만 변경되었을 때 페이지 전체를 다시 로드하지 않도록 개발 서버에서 사용하는 기법입니다. 변경사항이 브라우저에 즉시 반영되는 경우가 많으므로 애플리케이션을 개발하면서 수정하고 확인하는 과정을 줄일 수 있습니다. 현재 일반적인 JavaScript 기반 핫 모듈 교체(HMR)는 지원하지 않지만, 다음과 같은 특정 형태의 HMR은 사용할 수 있습니다:
- 전역 스타일시트 (
styles빌드 옵션) - 컴포넌트 스타일시트 (인라인 및 파일 기반)
- 컴포넌트 템플릿 (인라인 및 파일 기반)
HMR 기능은 자동으로 활성화되며, 사용하기 위해 코드나 설정을 변경할 필요는 없습니다.
Angular는 파일 기반(templateUrl/styleUrl/styleUrls)과 인라인(template/styles) 컴포넌트 스타일 및 템플릿에 모두 HMR을 지원합니다.
빌드 시스템은 스타일시트만 변경되었다고 감지하면 애플리케이션 코드를 최소한으로 컴파일하고 처리하려고 합니다.
원한다면 개발 서버의 hmr 옵션을 false로 설정해서 HMR 기능을 비활성화할 수 있습니다.
명령줄에서도 다음과 같이 변경할 수 있습니다:
ng serve --no-hmr
개발 서버로 Vite 사용하기
Angular CLI는 현재 Vite를 개발 서버 용도로만 사용합니다.
Vite는 자체 빌드 시스템을 사용하지 않더라도 클라이언트 사이드 지원을 갖춘 완전한 개발 서버를 제공하며, 의존성이 적은 npm 패키지로 배포됩니다.
따라서 종합적인 개발 서버 기능을 제공하기에 적합합니다.
현재 개발 서버는 새 빌드 시스템으로 메모리에 애플리케이션의 개발 빌드를 생성하고, 그 결과를 Vite에 전달해서 애플리케이션을 제공합니다.
Vite의 사용은 Webpack 기반 개발 서버와 마찬가지로 Angular CLI의 dev-server 빌더 안에 캡슐화되어 있으며, 현재 직접 설정할 수는 없습니다.
사전 번들링
사전 번들링은 개발 서버를 사용할 때 빌드와 재빌드 시간을 줄여줍니다. Vite는 Angular CLI에서 기본적으로 활성화되는 사전 번들링 기능을 제공합니다. 사전 번들링 과정은 프로젝트의 모든 서드 파티 의존성을 분석하고, 개발 서버를 처음 실행할 때 처리합니다. 이 과정을 거치면 재빌드하거나 개발 서버를 실행할 때마다 프로젝트의 의존성을 다시 빌드하고 번들링할 필요가 없습니다.
대부분은 추가 설정이 필요하지 않습니다. 다만 다음과 같은 상황에서는 설정을 조정해야 할 수 있습니다:
loader옵션처럼 의존성 내부의 import에 대한 로더 동작을 변경하는 경우npm link처럼 개발을 위해 의존성을 로컬 코드에 심볼릭 링크로 연결하는 경우- 의존성의 사전 번들링 과정에서 발생한 오류를 우회하는 경우
프로젝트에서 필요하다면 사전 번들링을 완전히 비활성화하거나 특정 의존성을 제외할 수 있습니다.
이 설정에는 dev-server 빌더의 prebundle 옵션을 사용합니다.
특정 의존성을 제외하려면 prebundle.exclude 옵션을 사용하세요:
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"prebundle": {
"exclude": ["some-dep"]
}
},
prebundle의 기본값은 true이며, false로 설정하면 사전 번들링을 완전히 비활성화합니다.
다만 사전 번들링을 비활성화하면 재빌드 시간이 늘어나므로, 특정 의존성만 제외하는 것을 권장합니다.
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"prebundle": false
},
새로운 기능
애플리케이션 빌드 시스템의 주요 이점 중 하나는 빌드와 재빌드 속도가 개선된다는 점입니다.
새 애플리케이션 빌드 시스템은 이 외에도 browser 빌더에는 없는 기능을 제공합니다.
중요: 여기서 설명하는 application 빌더의 새 기능은 기본적으로 @angular-devkit/build-angular:karma 테스트 빌더와 호환되지 않습니다.
이 빌더가 내부적으로 browser 빌더를 사용하기 때문입니다.
해당 빌더의 builderMode 옵션을 application으로 설정하면 application 빌더를 사용하도록 선택할 수 있습니다.
@angular/build:karma와 @angular/build:unit-test 빌더는 항상 application 빌더를 사용합니다.
문제를 발견하면 여기에 알려주세요.
define으로 빌드 시점에 값 교체하기
define 옵션을 사용하면 코드에 있는 식별자를 빌드 시점에 다른 값으로 교체할 수 있습니다.
서드 파티 빌더를 사용하는 일부 커스텀 Webpack 설정에서 사용하던 Webpack의 DefinePlugin과 비슷하게 동작합니다.
이 옵션은 angular.json 설정 파일이나 명령줄에서 사용할 수 있습니다.
값이 일정하고 소스 관리에 포함할 수 있다면 angular.json에 define을 설정하는 것이 유용합니다.
설정 파일에서는 객체 형태로 옵션을 지정합니다. 객체의 키는 교체할 식별자이며, 객체의 값은 해당 식별자를 대체할 값입니다. 예를 들어 다음과 같이 지정합니다:
"build": {
"builder": "@angular/build:application",
"options": {
...
"define": {
"SOME_NUMBER": "5",
"ANOTHER": "'this is a string literal, note the extra single quotes'",
"REFERENCE": "globalThis.someValue.noteTheAbsentSingleQuotes"
}
}
}
도움말: 설정 파일에서는 대체할 값을 모두 문자열로 정의합니다. 실제 문자열 리터럴로 교체하려면 작은따옴표로 감싸야 합니다. 이 방식으로 유효한 JSON 타입이나 다른 식별자도 대체 값으로 사용할 수 있습니다.
git 커밋 해시나 환경 변수처럼 빌드를 실행할 때마다 달라질 수 있는 값은 명령줄에서 지정하는 것이 좋습니다.
CLI는 명령줄의 --define 값과 angular.json의 define 값을 병합해서 빌드에 모두 포함합니다.
같은 식별자가 양쪽에 있으면 명령줄의 값이 우선합니다.
명령줄에서는 --define 옵션에 IDENTIFIER=VALUE 형식으로 값을 지정합니다.
ng build --define SOME_NUMBER=5 --define "ANOTHER='these will overwrite existing'"
환경 변수도 선택적으로 빌드에 포함할 수 있습니다. Windows가 아닌 셸에서는 원한다면 해시 리터럴 주변의 따옴표를 직접 이스케이프할 수 있습니다. 다음 예제는 bash 계열 셸을 가정하지만, 다른 셸에서도 비슷한 동작을 사용할 수 있습니다.
export MY_APP_API_HOST="http://example.com"
export API_RETRY=3
ng build --define API_HOST=\'$MY_APP_API_HOST\' --define API_RETRY=$API_RETRY
어느 방법을 사용하든 빌드 중 타입 검사 오류가 발생하지 않도록 TypeScript가 식별자의 타입을 알아야 합니다.
애플리케이션 소스 코드에 다음과 비슷한 내용의 타입 정의 파일(예: src/types.d.ts)을 추가하면 됩니다:
declare const SOME_NUMBER: number;
declare const ANOTHER: string;
declare const GIT_HASH: string;
declare const API_HOST: string;
declare const API_RETRY: number;
기본 프로젝트 설정은 소스 디렉터리에 있는 타입 정의 파일을 모두 사용하도록 이미 구성되어 있습니다. 프로젝트의 TypeScript 설정을 변경했다면 새로 추가한 타입 정의 파일을 참조하도록 조정해야 할 수도 있습니다.
중요: 이 옵션은 컴포넌트나 디렉티브 데코레이터 같은 Angular 메타데이터 안의 식별자를 교체하지 않습니다.
파일 확장자별 로더 설정하기
중요: 이 기능은 application 빌더에서만 사용할 수 있습니다.
일부 프로젝트는 특정 확장자를 가진 모든 파일을 어떻게 로드하고 애플리케이션에 번들링할지 제어해야 할 수 있습니다.
application 빌더를 사용한다면 loader 옵션으로 처리할 수 있습니다.
이 옵션은 지정한 파일 확장자에 사용할 로더 타입을 정의합니다.
해당 확장자의 파일은 애플리케이션 코드에서 import 문이나 동적 import 표현식으로 사용할 수 있습니다.
사용할 수 있는 로더는 다음과 같습니다:
text- 내용을 인라인으로 포함하고 기본 내보내기(default export)로string을 제공합니다.binary- 내용을 인라인으로 포함하고 기본 내보내기로Uint8Array를 제공합니다.file- 애플리케이션 빌드 결과물 경로에 파일을 생성하고, 실행 중 사용할 파일 위치를 기본 내보내기로 제공합니다.dataurl- 내용을 데이터 URL로 인라인으로 포함합니다.base64- 내용을 Base64로 인코딩된 문자열로 인라인으로 포함합니다.empty- 내용을 빈 것으로 취급하고 번들에 포함하지 않습니다.
empty는 자주 사용하지 않지만, 제거해야 하는 번들러 전용 import가 포함된 서드 파티 라이브러리와 호환성을 유지할 때 유용할 수 있습니다.
예를 들어 CSS 파일의 사이드 이펙트 import(import 'my.css';)는 브라우저에서 효과가 없습니다.
이 경우 프로젝트에서 empty를 사용하고, CSS 파일을 styles 빌드 옵션에 추가하거나 다른 주입 방식을 사용할 수 있습니다.
로더 옵션은 객체 형태이며, 키에 파일 확장자를 지정하고 값에 로더 타입을 지정합니다.
SVG 파일의 내용을 번들링된 애플리케이션에 인라인으로 포함하려면 다음과 같이 빌드 옵션을 사용합니다:
"build": {
"builder": "@angular/build:application",
"options": {
...
"loader": {
".svg": "text"
}
}
}
그러면 SVG 파일을 import할 수 있습니다:
import contents from './some-file.svg';
console.log(contents); // <svg>...</svg>
또한 빌드 중 타입 검사 오류가 발생하지 않도록 TypeScript가 import의 모듈 타입을 알아야 합니다.
애플리케이션 소스 코드에 다음과 같거나 비슷한 내용의 타입 정의 파일(예: src/types.d.ts)을 추가하면 됩니다:
declare module '*.svg' {
const content: string;
export default content;
}
기본 프로젝트 설정은 소스 디렉터리에 있는 타입 정의 파일(.d.ts 파일)을 모두 사용하도록 이미 구성되어 있습니다.
프로젝트의 TypeScript 설정을 변경했다면 새로 추가한 타입 정의 파일을 참조하도록 tsconfig를 조정해야 할 수도 있습니다.
import 어트리뷰트로 로더 설정하기
일부 파일만 특정 방식으로 로드해야 한다면 파일별로 로딩 동작을 제어할 수 있습니다.
import 문과 import 표현식에서 사용할 수 있는 loader import 어트리뷰트로 설정합니다.
import 어트리뷰트를 지정하면 JS/TS 처리와 loader 빌드 옵션 값을 포함한 다른 모든 로딩 동작보다 우선합니다.
기본적으로 지원하지 않는 파일 타입의 모든 파일에 일반적인 로딩 방식을 지정하려면 loader 빌드 옵션을 권장합니다.
import 어트리뷰트는 다음 로더 값을 지원합니다:
text- 내용을 인라인으로 포함하고 기본 내보내기(default export)로string을 제공합니다.binary- 내용을 인라인으로 포함하고 기본 내보내기로Uint8Array를 제공합니다.file- 애플리케이션 빌드 결과물 경로에 파일을 생성하고, 실행 중 사용할 파일 위치를 기본 내보내기로 제공합니다.dataurl- 내용을 데이터 URL로 인라인으로 포함합니다.base64- 내용을 Base64로 인코딩된 문자열로 인라인으로 포함합니다.
import 어트리뷰트를 사용하려면 추가로 TypeScript의 module 옵션을 esnext 또는 preserve로 설정해야 TypeScript가 애플리케이션 코드를 정상적으로 빌드할 수 있습니다.
새 프로젝트는 기본적으로 preserve를 사용하므로 변경할 필요가 없습니다.
현재 TypeScript는 import 어트리뷰트 값을 기반으로 하는 타입 정의를 지원하지 않습니다.
따라서 @ts-expect-error/@ts-ignore를 사용하거나, 개별 타입 정의 파일을 사용해야 합니다(해당 파일을 항상 같은 로더 어트리뷰트로 import한다고 가정합니다).
예를 들어 SVG 파일을 텍스트로 import하려면 다음과 같이 작성합니다:
// @ts-expect-error TypeScript는 아직 어트리뷰트를 기반으로 타입을 제공할 수 없습니다.
import contents from './some-file.svg' with {loader: 'text'};
비동기 함수 안의 import 표현식으로도 같은 작업을 할 수 있습니다.
async function loadSvg(): Promise<string> {
// @ts-expect-error TypeScript는 아직 어트리뷰트를 기반으로 타입을 제공할 수 없습니다.
return import('./some-file.svg', {with: {loader: 'text'}}).then((m) => m.default);
}
import 표현식에서 loader 값은 정적으로 분석할 수 있도록 문자열 리터럴이어야 합니다.
문자열 리터럴이 아니면 경고가 표시됩니다.
file 로더는 fetch(), 이미지 엘리먼트의 src 설정 또는 비슷한 방식으로 실행 중 파일을 로드할 때 유용합니다.
// @ts-expect-error TypeScript는 아직 어트리뷰트를 기반으로 타입을 제공할 수 없습니다.
import imagePath from './image.webp' with {loader: 'file'};
console.log(imagePath); // media/image-ULK2SIIB.webp
base64 로더는 파일을 인코딩된 문자열로 번들에 직접 포함한 뒤, 나중에 데이터 URL을 구성하는 데 사용할 때 유용합니다.
// @ts-expect-error TypeScript는 아직 어트리뷰트를 기반으로 타입을 제공할 수 없습니다.
import logo from './logo.png' with {loader: 'base64'};
console.log(logo); // "iVBORw0KGgoAAAANSUhEUgAA..."
dataurl 로더는 정적 리소스를 완전한 데이터 URL로 인라인으로 포함합니다.
// @ts-expect-error TypeScript는 아직 어트리뷰트를 기반으로 타입을 제공할 수 없습니다.
import icon from './icon.svg' with {loader: 'dataurl'};
console.log(icon); // "data:image/svg+xml;..."
위 코드 주석처럼 프로덕션 빌드에서는 장기 캐싱을 위해 경로에 해시가 자동으로 추가됩니다.
도움말: 개발 서버에서 loader 어트리뷰트로 Node.js 패키지의 파일을 import한다면, 개발 서버의 prebundle 옵션을 사용해서 해당 패키지를 사전 번들링에서 제외해야 합니다.
import/export 조건
프로젝트에서 빌드 종류에 따라 특정 import 경로를 서로 다른 파일에 연결해야 할 수 있습니다.
예를 들어 ng serve에서는 디버깅이나 개발 전용 코드를 사용하지만, ng build에서는 개발용 기능이나 정보가 없는 코드를 사용해야 할 때 유용합니다.
이런 요구사항을 지원하기 위해 다음 import/export 조건이 자동으로 적용됩니다:
- 최적화된 빌드에는
production조건을 활성화합니다. - 최적화하지 않은 빌드에는
development조건을 활성화합니다. - 브라우저 빌드 결과물 코드에는
browser조건을 활성화합니다.
빌드의 최적화 여부는 optimization 옵션 값으로 판단합니다.
optimization을 true로 설정하거나, 구체적으로 optimization.scripts를 true로 설정하면 최적화된 빌드로 간주합니다.
이 구분은 ng build와 ng serve에 모두 적용됩니다.
새 프로젝트에서 ng build는 기본적으로 최적화되며, ng serve는 기본적으로 최적화되지 않습니다.
애플리케이션 코드에서 이 조건을 유용하게 활용하는 방법은 하위 경로 import와 결합하는 것입니다. 다음 import 문을 사용한다면:
import {verboseLogging} from '#logger';
package.json의 imports 필드에서 파일을 바꿀 수 있습니다:
{
...
"imports": {
"#logger": {
"development": "./src/logging/debug.ts",
"default": "./src/logging/noop.ts"
}
}
}
SSR도 사용하는 애플리케이션은 browser 조건으로 브라우저 코드와 서버 코드를 전환할 수 있습니다:
{
...
"imports": {
"#crashReporter": {
"browser": "./src/browser-logger.ts",
"default": "./src/server-logger.ts"
}
}
}
이 조건은 Node.js 패키지와 패키지에 정의된 exports에도 적용됩니다.
도움말: 현재 fileReplacements 빌드 옵션을 사용한다면 이 기능으로 대체할 수 있을지도 모릅니다.
알려진 문제
새 빌드 시스템을 사용하면서 마주칠 수 있는 문제가 현재 몇 가지 알려져 있습니다. 이 목록은 최신 상태를 유지하도록 갱신됩니다. 이 문제 때문에 새 빌드 시스템을 사용하기 어렵다면 나중에 다시 확인해 주세요. 문제가 해결되었을 수도 있습니다.
웹 워커 코드의 타입 검사와 중첩 웹 워커 처리
애플리케이션 코드에서는 browser 빌더가 지원하는 것과 같은 문법(new Worker(new URL('<workerfile>', import.meta.url)))으로 웹 워커를 사용할 수 있습니다.
다만 현재 워커 내부의 코드는 TypeScript 컴파일러가 타입을 검사하지 않습니다.
TypeScript 코드를 지원하지만 타입 검사는 수행하지 않는다는 의미입니다.
또한 빌드 시스템은 중첩 워커를 처리하지 않습니다.
중첩 워커는 다른 워커 파일 안에서 생성한 워커 인스턴스를 말합니다.
ESM 기본 import와 네임스페이스 import
TypeScript는 기본적으로 기본 내보내기(default export)를 네임스페이스 import로 가져온 뒤 호출 표현식에서 사용하는 것을 허용합니다.
하지만 이 동작은 ECMAScript 명세와 다릅니다.
새 빌드 시스템의 기반 번들러인 esbuild는 명세를 준수하는 ESM 코드를 기대합니다.
이제 빌드 시스템은 애플리케이션에서 패키지를 잘못된 import 방식으로 사용하면 경고를 표시합니다.
다만 TypeScript가 올바른 사용 방식을 허용하도록 애플리케이션의 tsconfig 파일에서 TypeScript 옵션을 활성화해야 합니다.
esModuleInterop 옵션을 활성화하면 ECMAScript 명세에 더 부합하게 동작하며, TypeScript 팀도 이 옵션을 권장합니다.
옵션을 활성화한 뒤에는 해당 패키지의 import를 ECMAScript에 맞는 형식으로 변경할 수 있습니다.
moment 패키지를 예로 들면 다음 애플리케이션 코드는 실행 중 오류를 발생시킵니다:
import * as moment from 'moment';
console.log(moment().format());
빌드에서는 잠재적인 문제를 알리기 위해 다음과 비슷한 경고를 표시합니다:
▲ [WARNING] Calling "moment" will crash at run-time because it's an import namespace object, not a function [call-import-namespace]
src/main.ts:2:12:
2 │ console.log(moment().format());
╵ ~~~~~~
Consider changing "moment" to a default import instead:
src/main.ts:1:7:
1 │ import * as moment from 'moment';
│ ~~~~~~~~~~~
╵ moment
애플리케이션의 TypeScript 옵션 esModuleInterop을 활성화하고 import를 다음과 같이 변경하면 실행 중 오류와 경고를 피할 수 있습니다:
import moment from 'moment';
console.log(moment().format());
지연 로딩 모듈에서 순서에 의존하는 사이드 이펙트 import
특정 실행 순서에 의존하는 import 문을 여러 지연 로딩 모듈에서 사용하면 최상위 구문이 순서에 맞지 않게 실행될 수 있습니다.
사이드 이펙트가 있는 모듈을 사용하는 경우에 발생하므로 흔한 문제는 아니며, polyfills 옵션에는 적용되지 않습니다.
기반 번들러의 결함으로 발생하는 문제이며, 이후 업데이트에서 해결될 예정입니다.
중요: 사용하는 빌드 시스템과 관계없이, 가능하면 모듈 외부에 사이드 이펙트를 일으키는 모듈은 폴리필을 제외하고 사용하지 않는 것이 좋습니다. 그러면 이 문제도 피할 수 있습니다. 모듈 외부에 사이드 이펙트를 일으키는 모듈은 애플리케이션 크기와 실행 성능에도 부정적인 영향을 줄 수 있습니다.
빌드 결과물 위치 변경
기본적으로 application 빌더로 빌드가 성공하면 번들은 dist/<project-name>/browser 디렉터리에 생성됩니다(browser 빌더의 결과물 위치는 dist/<project-name>입니다).
기존 위치에 의존하는 일부 도구 모음은 이 변경 때문에 제대로 동작하지 않을 수 있습니다.
이 경우 필요에 맞게 빌드 결과물 경로 설정할 수 있습니다.
버그 제보
문제와 기능 요청은 GitHub에 등록해 주세요.
팀이 문제를 해결하는 데 도움이 되도록 가능하면 최소한의 재현 예제를 제공해 주세요.