webpack 설정을 유지한 채 Rspack 1.x로 옮길 때 달라지는 지점과 비호환 플러그인 처리
Rspack은 Rust로 작성된 번들러이면서 webpack의 설정 스키마를 그대로 받아들이도록 설계됐다. Vite로의 전면 전환이 부담스러운 webpack 기반 프로젝트에서 중간 선택지로 언급되는 이유가 여기에 있다.
이 글은 Rspack 1.x를 기준으로, webpack 설정을 유지하면서 옮길 때 실제로 달라지는 지점과 비호환 플러그인 처리 방법을 다룬다. 2026년 4월 Rspack 2.0이 이미 정식 릴리즈됐고, 현재 시점에서 @rspack/core를 별다른 옵션 없이 설치하면 2.0이 설치된다. 아래 내용을 1.x 기준으로 진행하려면 @rspack/core@^1처럼 버전을 고정해야 하며, 상당수 옵션·플러그인 API는 2.0에서도 동일하게 유효하다.
webpack과 Rspack의 처리 모델 차이
webpack은 JavaScript로 작성돼 Node.js 이벤트 루프 위에서 돈다. 의존성 그래프 구축, 코드 변환, 트리 셰이킹, 청크 분리 같은 파이프라인이 본질적으로 단일 스레드에 묶여 있고, thread-loader는 특정 로더 단계에만 국한된 병렬화다.
Rspack은 Rust로 작성됐고, 위 파이프라인 대부분을 멀티스레드로 돌린다. webpack의 HMR도 변경된 모듈 중심으로 부분 재컴파일을 하지만, Rspack은 더 정밀한 증분 그래프와 네이티브 코드 실행으로 재빌드 지연 자체를 줄인다.
공개된 사례로는 Mews의 Rspack 전환 리포트가 대규모 모노레포에서 빌드 시간이 3분에서 10초로 줄어 약 94% 단축됐다고 밝혔고, Yelp 엔지니어링 블로그는 캐시가 완전히 워밍된 상태 기준 최대 80% 단축을 보고했다. 측정 조건에 따라 편차가 크므로 자기 프로젝트 기준으로 반드시 재검증하는 게 좋다.
세 가지 호환성 레이어
Rspack이 내세우는 호환성은 세 레이어로 구분해서 이해하면 판단이 편하다.
| 레이어 | 호환 범위 | 주의 사항 |
|---|---|---|
| 설정 파일 | entry, output, resolve, module.rules, optimization 등 대부분 동일 |
극히 일부 webpack 내부 전용 옵션은 예외 |
| 로더 | babel-loader, css-loader, sass-loader 등 커뮤니티 로더 대부분 사용 가능 |
JS 기반 로더는 네이티브 대비 느림 |
| 플러그인 | compiler.hooks, compilation.hooks 등 훅 체계 지원 |
webpack 내부 API 직접 호출 시 비호환 |
최소한의 변경으로 옮기기
패키지 교체
npm uninstall webpack webpack-cli webpack-dev-server
npm install -D @rspack/core@^1 @rspack/cli@^1 @rspack/dev-server@^1package.json 스크립트를 바꾼다.
{
"scripts": {
"build": "rspack build",
"dev": "rspack serve"
}
}설정 파일 이름은 webpack.config.js를 그대로 써도 되고 rspack.config.js로 바꿔도 된다. 스키마가 사실상 같기 때문이다.
빌드 시도로 비호환 요소 파악하기
Rspack은 별도의 진단 리포트 도구를 제공하지 않는다. 실제로는 기존 설정으로 그냥 빌드를 돌려보고, 콘솔에 뜨는 경고와 오류 메시지에서 비호환 옵션·플러그인을 확인하는 방식이 된다.
npx @rspack/cli build --config webpack.config.js여기서 나오는 경고들을 수집해두면 이후 교체 작업 순서를 정하는 데 도움이 된다.
내장 플러그인으로 바꿔치기
Rspack 1.x는 자주 쓰이는 webpack 플러그인들의 Rust 기반 내장 버전을 제공한다.
const rspack = require('@rspack/core')
module.exports = {
plugins: [
new rspack.HtmlRspackPlugin({
template: './public/index.html',
}),
new rspack.CssExtractRspackPlugin(),
new rspack.CopyRspackPlugin({
patterns: [{ from: 'public' }],
}),
new rspack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}),
],
}CopyRspackPlugin의 to는 output.path 기준 상대 경로다. 출력 디렉터리가 이미 dist라면 to: 'dist'로 두면 dist/dist/에 복사된다. 루트에 그대로 두려면 위 예시처럼 to를 생략하거나 to: '.'로 지정하는 것이 맞다.
JS 로더에서 네이티브 로더로
babel-loader는 Rspack에서도 동작한다. 다만 JS 실행 오버헤드 때문에 Rust 네이티브 변환보다 느리고, 전환 후 "생각보다 안 빠르다"는 인상의 가장 흔한 원인이 여기다. Rspack은 내장 builtin:swc-loader 사용을 권장한다.
ts-loader도 완전 비호환은 아니지만, webpack 내부 인터페이스를 참조하는 부분이 있어 완전 호환은 보장되지 않는다. TypeScript 컴파일은 builtin:swc-loader로 위임하고 타입 체크는 별도 프로세스로 돌리는 조합이 실무에서 무난하다.
module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: { syntax: 'typescript', tsx: true },
transform: { react: { runtime: 'automatic' } },
},
},
},
},
],
},
}커스텀 Babel 플러그인이 있을 때
babel-plugin-styled-components 같은 Babel 전용 변환이 필요하면 builtin:swc-loader만으로 대체하기 어렵다. 이때 선택지는 두 가지다.
첫째, SWC 플러그인 생태계에 대응 구현이 있으면 그쪽을 쓴다. styled-components라면 @swc/plugin-styled-components를 SWC 설정에 붙이는 방식이 있다.
// 개념적 예시 — SWC 설정에 styled-components 플러그인 붙이기
{
loader: 'builtin:swc-loader',
options: {
jsc: {
experimental: {
plugins: [['@swc/plugin-styled-components', { displayName: true, ssr: true }]],
},
},
},
}둘째, 대체 구현이 없으면 필요한 파일 범위에만 babel-loader를 뒤에 체이닝해서 남긴다. 이때 파일명 규칙으로 분리하는 방식은 실제 코드베이스에서 잘 작동하지 않으므로, 특정 경로(packages/legacy/** 등)나 resource 조건을 기준으로 좁히는 것이 현실적이다.
비호환 플러그인 처리
어디서 비호환이 발생하나
Rspack 공식 플러그인 호환성 목록은 개별 플러그인 단위로 지원 여부를 명시한다. 비호환이 발생하는 패턴은 대체로 두 가지다.
webpack 내부 API 직접 호출. NormalModuleFactory 내부 인터페이스나 compilation.hooks.processAssets를 webpack 전용 유틸리티와 조합해 쓰는 플러그인은 그대로 동작하지 않는다. compiler.webpack.NormalModule 같은 경로로 내부 객체를 꺼내 쓰는 플러그인은 경계해야 한다.
webpack 내부 객체 형태 가정. Rspack이 API를 에뮬레이션해도 내부 객체 구조 자체가 다르면 예외 없이 런타임에 조용히 실패하는 경우가 있다.
주요 플러그인 대체 경로
| webpack 플러그인 | Rspack 대체제 | 비고 |
|---|---|---|
html-webpack-plugin |
rspack.HtmlRspackPlugin |
내장, Rust 기반 |
mini-css-extract-plugin |
rspack.CssExtractRspackPlugin |
내장 |
copy-webpack-plugin |
rspack.CopyRspackPlugin |
내장 |
fork-ts-checker-webpack-plugin |
@rspack-contrib/ts-checker-rspack-plugin |
별도 프로세스 타입 체크 |
webpack-bundle-analyzer |
@rsdoctor/rspack-plugin |
번들 구조 분석 |
unplugin 기반은 서브패스만 바꾸면 된다
unplugin-icons, unplugin-auto-import 같은 unplugin 생태계 도구들은 /rspack 서브패스로 임포트 경로만 바꾸면 그대로 동작한다.
const Icons = require('unplugin-icons/rspack').default
module.exports = {
plugins: [
Icons({ compiler: 'jsx', jsx: 'react' }),
],
}fork-ts-checker 교체
npm uninstall fork-ts-checker-webpack-plugin
npm install -D @rspack-contrib/ts-checker-rspack-pluginconst { TsCheckerRspackPlugin } = require('@rspack-contrib/ts-checker-rspack-plugin')
module.exports = {
plugins: [
new TsCheckerRspackPlugin({
typescript: { configFile: 'tsconfig.json' },
}),
],
}주의해야 할 지점들
Module Federation
Rspack은 Module Federation v2 스펙을 지원한다(2026년 기준). webpack 5의 원래 Module Federation(v1)으로 빌드된 리모트를 Rspack 호스트에서 소비할 때, shared 객체 형태가 맞지 않으면 런타임에 React 중복 인스턴스 경고가 발생할 수 있다. Rspack 1.x에서 기본 형태의 MF를 붙일 때는 내장 컨테이너 플러그인을 쓰는 경로가 공식 예시에 해당한다.
const rspack = require('@rspack/core')
module.exports = {
plugins: [
new rspack.container.ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
}MF v2 기능을 적극적으로 쓴다면 @module-federation/enhanced/rspack을 사용하는 경로가 별도로 있으므로 공식 문서를 확인하자.
SSR 구성에서 CSS 추출 로더
SSR 프로젝트에서는 서버 번들 쪽에서 mini-css-extract-plugin 로더를 조건부로 제거하는 코드를 많이 쓴다. Rspack에서는 대응되는 로더 참조가 rspack.CssExtractRspackPlugin.loader로 달라지므로, 서버/클라이언트 분기 코드에서 이 참조도 함께 바꿔야 한다.
// 개념적 예시 — 서버/클라이언트 분기
const isServer = process.env.BUILD_TARGET === 'server'
const rspack = require('@rspack/core')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: isServer
? ['css-loader']
: [rspack.CssExtractRspackPlugin.loader, 'css-loader'],
},
],
},
plugins: [
...(isServer ? [] : [new rspack.CssExtractRspackPlugin()]),
],
}Angular 프로젝트
Angular CLI는 webpack 전용 내부 API에 강하게 의존하는 Angular 전용 플러그인들을 사용한다. Rspack이 웹팩 훅 체계 상당 부분을 재현하지만, 이 계층까지 매끄럽게 대체하기는 어렵다는 것이 커뮤니티 리포트들의 공통된 평가다. React·Vue 기반 프로젝트와는 난이도가 다르므로, 현시점에서는 Angular 스택에서의 전환은 보류하거나 별도 실험 프로젝트로 다루는 편이 낫다.
마이그레이션 흐름
프로젝트 규모별로 대략적인 소요 시간을 잡아두면 일정 협상이 쉬워진다. 단순 React + TypeScript SPA는 반나절 이내에 끝나는 경우가 많고, 커스텀 로더·플러그인이 많은 모노레포는 하루~이틀 정도가 현실적인 범위다. 실제 시간은 커스텀 Babel 플러그인 수, MF 리모트 개수, SSR 여부에 따라 편차가 크다.
번들 분석과 검증
마이그레이션 후 검증에는 webpack-bundle-analyzer 대신 Rsdoctor를 붙여볼 만하다. webpack과 Rspack 양쪽을 지원하고, 빌드 병목과 번들 구성을 함께 보여준다.
npm install -D @rsdoctor/rspack-pluginconst { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin')
module.exports = {
plugins: [
process.env.RSDOCTOR && new RsdoctorRspackPlugin({
supports: { generateTileGraph: true },
}),
].filter(Boolean),
}RSDOCTOR=true rspack build2026년 시점의 포지셔닝
앞서 언급한 대로 Rspack 2.0이 2026년 4월에 릴리즈됐다. 공식 발표에 따르면 번들러 자체가 순수 ESM으로 전환됐고, @rspack/dev-server의 의존성 그래프와 설치 크기가 크게 줄었으며, 특정 벤치마크에서 1.0 대비 "up to 100% faster"라는 표현으로 성능 개선을 공개했다(측정 조건은 원문 참조).
지금 시점에서 1.x 기준의 마이그레이션 자료를 참고하는 이유는, 상당수 팀이 실무 코드베이스에서는 1.x 안정판에서 시작해 검증 후 2.0으로 넘어가는 순서를 택하기 때문이다. @rspack/core@^1로 버전을 고정한 뒤 이 문서의 순서대로 옮기고, 이후 2.0 릴리즈 노트에서 브레이킹 체인지 목록을 확인하는 흐름이 안전하다.
참고 자료
- Rspack 공식 문서 — webpack에서 마이그레이션
- Rspack 공식 문서 — 플러그인 호환성 목록
- Rspack 공식 문서 — 커뮤니티 플러그인 호환성
- Rspack 공식 문서 — Module Federation
- Rspack 공식 블로그 — v1.0 발표
- Rspack 공식 블로그 — v2.0 발표
- Mews 엔지니어링 블로그 — webpack에서 Rspack으로
- Yelp 엔지니어링 블로그 — webpack에서 Rspack으로
- GitHub — ts-checker-rspack-plugin
- Rspack 공식 문서 — Rsdoctor 사용 가이드
- Brian Birtles 블로그 — Rspack 전환 후 배운 것들