React Compiler는 여러분의 전체 코드베이스를 자동으로 최적화하도록 설계되었지만, 한 번에 모든 곳에 적용할 필요는 없어요. 점진적 도입은 롤아웃 과정에 대한 제어권을 주어서, 나머지 부분으로 확장하기 전에 앱의 작은 부분에서 컴파일러를 테스트할 수 있게 해줘요.
작은 곳에서 시작하면 컴파일러의 최적화에 대한 신뢰를 쌓는 데 도움이 돼요. 컴파일된 코드로 앱이 올바르게 동작하는지 확인하고, 성능 개선을 측정하고, 여러분의 코드베이스에 특화된 엣지 케이스를 식별할 수 있어요. 이 접근 방식은 안정성이 중요한 프로덕션 애플리케이션에서 특히 유용해요.
점진적 도입은 컴파일러가 찾을 수 있는 React 규칙 위반을 해결하기도 더 쉽게 만들어줘요. 전체 코드베이스의 위반을 한 번에 수정하는 대신, 컴파일러 적용 범위를 넓혀가면서 체계적으로 해결할 수 있어요. 이렇게 하면 마이그레이션이 관리하기 쉬워지고 버그가 발생할 위험을 줄일 수 있어요.
어떤 부분의 코드가 컴파일되는지 제어함으로써, 컴파일러 최적화의 실제 영향을 측정하기 위한 A/B 테스트도 실행할 수 있어요. 이 데이터는 전체 도입에 대한 정보에 기반한 결정을 내리는 데 도움이 되고, 팀에게 그 가치를 보여줄 수 있어요.
React Compiler를 점진적으로 도입하는 세 가지 주요 접근 방식이 있어요:
모든 접근 방식은 전체 롤아웃 전에 애플리케이션의 특정 부분에서 컴파일러를 테스트할 수 있게 해줘요.
Babel의 overrides 옵션을 사용하면 코드베이스의 다른 부분에 다른 플러그인을 적용할 수 있어요. 이건 디렉토리별로 React Compiler를 점진적으로 도입하는 데 이상적이에요.
특정 디렉토리에 컴파일러를 적용하는 것부터 시작하세요:
// babel.config.js
module.exports = {
plugins: [
// 모든 파일에 적용되는 전역 플러그인
],
overrides: [
{
test: './src/modern/**/*.{js,jsx,ts,tsx}',
plugins: [
'babel-plugin-react-compiler'
]
}
]
};
(여기서 test 필드는 어떤 파일들에 이 설정을 적용할지 결정하는 패턴이에요. ./src/modern/ 폴더 아래의 모든 JS, JSX, TS, TSX 파일에 컴파일러가 적용되는 거죠.)
자신감이 생기면, 더 많은 디렉토리를 추가하세요:
// babel.config.js
module.exports = {
plugins: [
// 전역 플러그인
],
overrides: [
{
test: ['./src/modern/**/*.{js,jsx,ts,tsx}', './src/features/**/*.{js,jsx,ts,tsx}'],
plugins: [
'babel-plugin-react-compiler'
]
},
{
test: './src/legacy/**/*.{js,jsx,ts,tsx}',
plugins: [
// 레거시 코드를 위한 다른 플러그인
]
}
]
};
(이렇게 하면 modern과 features 폴더에는 React Compiler를 적용하고, legacy 폴더에는 다른 설정을 적용할 수 있어요. 기존의 오래된 코드는 천천히 마이그레이션하면서 새로운 코드부터 컴파일러의 혜택을 받을 수 있는 거죠.)
override마다 컴파일러 옵션도 설정할 수 있어요:
// babel.config.js
module.exports = {
plugins: [],
overrides: [
{
test: './src/experimental/**/*.{js,jsx,ts,tsx}',
plugins: [
['babel-plugin-react-compiler', {
// 옵션들 ...
}]
]
},
{
test: './src/production/**/*.{js,jsx,ts,tsx}',
plugins: [
['babel-plugin-react-compiler', {
// 옵션들 ...
}]
]
}
]
};
(실험적인 코드와 프로덕션 코드에 다른 컴파일러 옵션을 적용할 수 있어요. 예를 들어, 실험적인 폴더에서는 더 공격적인 최적화를 시도하고, 프로덕션에서는 더 보수적인 설정을 사용할 수 있죠.)
최대한의 제어를 원한다면, compilationMode: 'annotation'을 사용해서 "use memo" 지시어로 명시적으로 옵트인한 컴포넌트와 훅만 컴파일할 수 있어요.
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'annotation',
}],
],
};
컴파일하고 싶은 함수의 시작 부분에 "use memo"를 추가하세요:
function TodoList({ todos }) {
"use memo"; // 이 컴포넌트를 컴파일 대상으로 옵트인
const sortedTodos = todos.slice().sort();
return (
<ul>
{sortedTodos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
function useSortedData(data) {
"use memo"; // 이 훅을 컴파일 대상으로 옵트인
return data.slice().sort();
}
(지시어(directive)라는 건 함수 본문의 맨 첫 줄에 문자열 리터럴을 넣는 방식이에요. "use strict"와 비슷한 방식이죠. React Compiler는 이 지시어를 보고 해당 함수를 컴파일 대상으로 인식해요.)
compilationMode: 'annotation'을 사용할 때는 다음을 해야 해요:
"use memo" 추가하기"use memo" 추가하기이렇게 하면 컴파일러의 영향을 평가하는 동안 어떤 컴포넌트가 컴파일되는지 정밀하게 제어할 수 있어요.
gating 옵션을 사용하면 기능 플래그를 사용해서 런타임에 컴파일을 제어할 수 있어요. 이건 A/B 테스트를 실행하거나 사용자 세그먼트에 따라 컴파일러를 점진적으로 롤아웃할 때 유용해요.
컴파일러는 최적화된 코드를 런타임 체크로 감싸요. 게이트가 true를 반환하면 최적화된 버전이 실행돼요. 그렇지 않으면 원본 코드가 실행돼요.
(쉽게 말해서, 컴파일된 코드와 원본 코드를 둘 다 가지고 있다가 런타임에 어떤 걸 사용할지 결정하는 거예요. 이렇게 하면 문제가 생겼을 때 기능 플래그만 끄면 바로 원본 코드로 돌아갈 수 있어서 안전해요.)
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
gating: {
source: 'ReactCompilerFeatureFlags',
importSpecifierName: 'isCompilerEnabled',
},
}],
],
};
게이팅 함수를 내보내는 모듈을 만드세요:
// ReactCompilerFeatureFlags.js
export function isCompilerEnabled() {
// 여러분의 기능 플래그 시스템을 사용하세요
return getFeatureFlag('react-compiler-enabled');
}
(여기서 getFeatureFlag는 여러분이 사용하는 기능 플래그 시스템이에요. LaunchDarkly, Split, 자체 구축한 시스템 등 어떤 것이든 사용할 수 있어요. 이 함수가 true를 반환하면 컴파일된 코드가 실행되고, false를 반환하면 원본 코드가 실행되는 거예요.)
도입 중에 문제가 발생하면:
"use no memo"를 사용하세요compilationMode: 'annotation' 사용을 고려하세요(특히 "use no memo" 지시어는 정말 유용해요. 특정 컴포넌트에서 문제가 발생했을 때, 그 컴포넌트만 컴파일에서 제외하고 나머지는 계속 컴파일러의 혜택을 받을 수 있거든요. 문제를 해결한 후에 지시어를 제거하면 돼요.)