LearnApplyShare

리액트 프로젝트에 SWC 적용하기

May 31, 2022 - [swc, cra, react]

리액트 프로젝트에 SWC 를 적용하며 경험한 삽질을 공유한다.

Background

지난 FEConf2021 에서 강동윤님의 발표를 통해서 SWC 에 대하여 처음 알게 되었다. https://min9nim.vercel.app/2021-11-01-feconf-2021-review/

swc 란 자바스크립트 컴파일러다. 기존에 Babel 이 하던 일의 대체제이다. 그런데 왜 swc 가 필요하냐? 바로 Babel 은 노드로 작성되었는데 CPU 자원을 많이 소모하는 컴파일과 같은 작업에서는 사실 노드를 사용하는 것이 적절하진 않기 때문이다. 반면에 swc 는 Rust 로 작성되어 있어서 컴파일러와 같은 녀석을 만들기에 적합하다. 여기서 더 적합하다고 판단하는 기준은 속도이다. 요즘 대다수의 프론트 개발에서 Babel 을 사용하고 있는데, 이렇게 무게감 있는 모듈과 작업을 왜 굳이 Node 기반으로 사용할까? 이 부분에 대한 의문에서부터 시작된 프로젝트라 할 수 있다. (비슷한 프로젝트로서 rome 도 있다. https://rome.tools )

아무튼, 나도 웹 빌드 시간이 너무 느리다는 지점에 크게 동의하고 있던 터라.. 빨라질 수 있다기에 (일반적으로 20배 이상) swc 를 사용해보고 싶었다.

Nextjs 로 유명한 Vercel 에서도 swc 프로젝트에 크게 공감을 했었는 지 swc 프로젝트의 오너?였던 강동윤님을 모셔갔고 작년에 Nextjs 새 버젼에 swc 를 기본 엔진으로 탑재시켰다. 실제로 사용해 보진 않았지만. 기회가 된다면 swc 때문에라도 Nextjs 를 꼭 힌번 사용해 보고 싶었다. 그러다 가만 생각을 해보니, 꼭 Nextjs 아니더라도 기본 CRA 프로젝트에 swc 를 적용할 수 있는 방법이 있지 않을까 해서 찾아 보았고 아래 포스트를 만났다.

https://jwchang0206.medium.com/make-create-react-app-faster-with-rust-6c75ffa8fdfd

위 글에서는 craco-swc 를 이용해서 손쉽게 CRA 프로젝트에 swc 를 사용하는 방법을 소개 하고 있다. 그래서 위 제시된 방법대로 craco 를 세팅하고.. (이걸 하느라 CRA 와 react 버젼까지 강제 업그레이드 되었다!) craco-swc 를 적용해 보았는데.. 빌드까지는 잘 되었지만 런타임에 React is not defined 오류가 발생했다.

짜증이 확 나서 이쯤에서 포기할까 하다가, 조금 더 관련 자료를 찾아보고 craco-swc 코드도 뒤져 보다가 다음과 같이 세팅을 해서 swc 적용에 성공하였다.

swc 설치 방법

  1. craco 설치 및 세팅

    yarn add -D @craco/craco

    craco 세팅방법은 craco 매뉴얼 참고. https://www.npmjs.com/package/@craco/craco


  1. swc 설치

    yarn add -D @swc/core @swc/helpers swc-loader

  1. swcPlugin.js
const { addAfterLoader, loaderByName, removeLoaders } = require('@craco/craco')

module.exports = {
    plugin: {
        overrideWebpackConfig: ({
            webpackConfig,
            pluginOptions,
            context: { paths },
        }) => {
            addAfterLoader(webpackConfig, loaderByName('babel-loader'), {
                test: /\.(js|mjs|jsx|ts|tsx)$/,
                include: paths.appSrc,
                loader: require.resolve('swc-loader'),
                options: pluginOptions,
            })
            removeLoaders(webpackConfig, loaderByName('babel-loader'))

            return webpackConfig
        },
    },
    options: {
        jsc: {
            target: 'es2015',
            externalHelpers: true,
            transform: {
                react: {
                    runtime: 'automatic',
                },
            },
            parser: {
                syntax: 'typescript',
                tsx: true,
                dynamicImport: true,
            },
        },
    },
}

위 swcPlugin.js 코드는 craco-swc 의 소스코드를 일부 수정한 것이다. 사실 craco-swc 는 위와 같은 설정들을 추상화시켜 놓은 것에 불과하다.

보다 자세한 swc 설정은 해당 문서 참고. https://swc.rs/docs/configuration/compilation


  1. craco.config.js
const swcPlugin = require('./swcPlugin')

module.exports = function ({ env }) {
    return {
        plugins: [
            swcPlugin,
        ],
    }
}

위와 같이 설정하니 제대로 동작이 되었다! 그런데 뭔가 빌드속도가 기대했던 것 만큼(20배 이상!) 빨라지진 않았다. 그래도 기존대비 50% 정도는 줄어든 것 같다!


SWC 적용 결과

AWS Amplify 환경에서 빌드 소요시간이 기존 114s 에서 59s 로 줄어듬.

swc 적용 전)

2022-05-30T02:38:11.794Z [INFO]: $ CI=false craco build
2022-05-30T02:38:13.180Z [INFO]: Creating an optimized production build...
2022-05-30T02:40:05.568Z [INFO]: Compiled with warnings.
2022-05-30T02:40:05.572Z [INFO]: Module not found: Error: Can't resolve './mock-data.local' in '/codebuild/output/src620371399/src/aurora-web/src/api'
                                 src/views/basic/channels/MediaTitleSection.tsx
                                 Line 16:21:  img elements must have an alt prop, either with meaningful text, or an empty string for decorative images  jsx-a11y/alt-text
                                 src/views/basic/extract/ExtractData/Methods.tsx
                                 Line 6:13:  'methods' is assigned a value but never used  no-unused-vars
                                 Line 6:13:  'methods' is assigned a value but never used  @typescript-eslint/no-unused-vars
                                 Line 6:22:  'set' is assigned a value but never used      no-unused-vars
                                 Line 6:22:  'set' is assigned a value but never used      @typescript-eslint/no-unused-vars
                                 src/views/basic/extract/ExtractStatus.tsx
                                 Line 18:9:  Expected { after 'if' condition  curly
                                 src/views/basic/settings/manage-member/DialogAllowRequest.tsx
                                 Line 7:8:  'NoticeIcon' is defined but never used  no-unused-vars
                                 Line 7:8:  'NoticeIcon' is defined but never used  @typescript-eslint/no-unused-vars
                                 Search for the keywords to learn more about each warning.
                                 To ignore, add // eslint-disable-next-line to the line before.
                                 File sizes after gzip:
2022-05-30T02:40:05.686Z [INFO]: 366.93 kB  build/static/js/main.60541054.js
                                 130.82 kB  build/static/js/735.db0611e8.chunk.js
2022-05-30T02:40:05.686Z [INFO]: 33.62 kB   build/static/js/44.6c567a22.chunk.js
                                 32.44 kB   build/static/js/17.ce93d508.chunk.js
                                 9.9 kB     build/static/js/834.71a00f40.chunk.js
                                 9.44 kB    build/static/js/343.269029be.chunk.js
                                 9.34 kB    build/static/js/87.a03e4915.chunk.js
                                 7.12 kB    build/static/js/249.f36068a1.chunk.js
                                 5.9 kB     build/static/js/22.f4b30e83.chunk.js
                                 5.41 kB    build/static/js/294.22630186.chunk.js
                                 5.03 kB    build/static/js/234.a17188e9.chunk.js
                                 4.74 kB    build/static/js/502.8c4c681f.chunk.js
                                 4.36 kB    build/static/js/792.953245fb.chunk.js
                                 4.34 kB    build/static/js/437.0423cb6e.chunk.js
                                 4.24 kB    build/static/js/728.5cf0ee71.chunk.js
                                 3.87 kB    build/static/js/244.be8b7ac9.chunk.js
                                 3.75 kB    build/static/js/523.c74a6b17.chunk.js
                                 3.74 kB    build/static/js/313.4fed6df2.chunk.js
                                 3.58 kB    build/static/js/131.ca7cc402.chunk.js
                                 3.55 kB    build/static/js/477.31912c72.chunk.js
                                 3.45 kB    build/static/js/165.211fa9bb.chunk.js
                                 3.2 kB     build/static/js/171.82b62e6c.chunk.js
                                 2.98 kB    build/static/css/44.54bb5762.chunk.css
2022-05-30T02:40:05.686Z [INFO]: 2.85 kB    build/static/js/170.8ee6c5a2.chunk.js
                                 2.68 kB    build/static/js/469.2d7ebb8e.chunk.js
                                 2.59 kB    build/static/js/667.7b45dc3f.chunk.js
                                 2.43 kB    build/static/js/966.4e58be79.chunk.js
                                 2.33 kB    build/static/js/867.54f256c2.chunk.js
                                 2.06 kB    build/static/js/664.08ef7faf.chunk.js
                                 2.01 kB    build/static/js/182.fa2cb8ed.chunk.js
                                 2.01 kB    build/static/js/319.65ef87a6.chunk.js
                                 1.91 kB    build/static/js/592.48207e9d.chunk.js
                                 1.8 kB     build/static/js/720.3d2710b5.chunk.js
                                 1.78 kB    build/static/js/787.88e3aee4.chunk.js
                                 1.66 kB    build/static/js/517.7b84126e.chunk.js
                                 1.41 kB    build/static/js/584.c1494bb2.chunk.js
                                 1.33 kB    build/static/js/74.fae9951c.chunk.js
                                 1.23 kB    build/static/css/main.8ae4e198.css
                                 1.22 kB    build/static/js/80.d5082c1a.chunk.js
                                 509 B      build/static/js/773.22552805.chunk.js
                                 308 B      build/static/js/683.2c3de2c4.chunk.js
2022-05-30T02:40:05.686Z [INFO]: 304 B      build/static/js/581.ff1bd043.chunk.js
                                 303 B      build/static/js/809.7651cc27.chunk.js
2022-05-30T02:40:05.687Z [INFO]: The project was built assuming it is hosted at /.
                                 You can control this with the homepage field in your package.json.
                                 The build folder is ready to be deployed.
                                 You may serve it with a static server:
                                 yarn global add serve
                                 serve -s build
                                 Find out more about deployment here:
                                 https://cra.link/deployment
2022-05-30T02:40:05.817Z [INFO]: Done in 114.06s.

swc 적용 후)

2022-06-02T09:26:55.423Z [INFO]: $ CI=false craco build
2022-06-02T09:26:56.775Z [INFO]: Creating an optimized production build...
2022-06-02T09:27:54.841Z [INFO]: Compiled with warnings.
2022-06-02T09:27:54.846Z [INFO]: src/routes/MainRoutes.tsx
                                 Line 222:7:  'routesByPathList' is assigned a value but never used  no-unused-vars
                                 Line 222:7:  'routesByPathList' is assigned a value but never used  @typescript-eslint/no-unused-vars
                                 src/views/basic/channels/MediaTitleSection.tsx
                                 Line 16:21:  img elements must have an alt prop, either with meaningful text, or an empty string for decorative images  jsx-a11y/alt-text
                                 src/views/basic/extract/ExtractData/Methods.tsx
                                 Line 38:22:  'set' is assigned a value but never used  no-unused-vars
                                 Line 38:22:  'set' is assigned a value but never used  @typescript-eslint/no-unused-vars
                                 src/views/basic/extract/ExtractData/SelectMethod.tsx
                                 Line 7:26:  'propEq' is defined but never used  no-unused-vars
                                 Line 7:26:  'propEq' is defined but never used  @typescript-eslint/no-unused-vars
                                 src/views/basic/extract/ExtractStatus.tsx
                                 Line 18:9:  Expected { after 'if' condition  curly
                                 src/views/basic/settings/manage-member/DialogAllowRequest.tsx
                                 Line 7:8:  'NoticeIcon' is defined but never used  no-unused-vars
                                 Line 7:8:  'NoticeIcon' is defined but never used  @typescript-eslint/no-unused-vars
                                 Search for the keywords to learn more about each warning.
                                 To ignore, add // eslint-disable-next-line to the line before.
                                 File sizes after gzip:
2022-06-02T09:27:54.968Z [INFO]: 360.41 kB  build/static/js/main.473cfe57.js
                                 130.77 kB  build/static/js/725.b9efa704.chunk.js
                                 33.63 kB   build/static/js/687.63a2d6a0.chunk.js
2022-06-02T09:27:54.968Z [INFO]: 29.35 kB   build/static/js/91.85086a9b.chunk.js
                                 11.02 kB   build/static/js/482.65711103.chunk.js
                                 9.13 kB    build/static/js/293.981cb83c.chunk.js
                                 8.54 kB    build/static/js/846.4bc26085.chunk.js
                                 5.43 kB    build/static/js/641.ec7669e9.chunk.js
                                 5.13 kB    build/static/js/145.255bb907.chunk.js
                                 4.83 kB    build/static/js/664.a2075eb2.chunk.js
                                 4.48 kB    build/static/js/307.637bd275.chunk.js
                                 4.26 kB    build/static/js/10.dd7d2c34.chunk.js
                                 4.14 kB    build/static/js/715.7f3c8bc5.chunk.js
                                 4.1 kB     build/static/js/175.aa467066.chunk.js
                                 3.77 kB    build/static/js/243.00df0cb1.chunk.js
                                 3.48 kB    build/static/js/400.c0959118.chunk.js
                                 3.48 kB    build/static/js/597.330a5a81.chunk.js
                                 3.46 kB    build/static/js/552.0e839635.chunk.js
                                 3.45 kB    build/static/js/861.15e8097b.chunk.js
                                 3.23 kB    build/static/js/154.ffc2599f.chunk.js
                                 3.1 kB     build/static/js/241.9ff8ae86.chunk.js
                                 2.98 kB    build/static/css/687.5f1a8b0c.chunk.css
                                 2.77 kB    build/static/js/941.5c8fd99a.chunk.js
                                 2.6 kB     build/static/js/759.adf4f0b1.chunk.js
                                 2.56 kB    build/static/js/389.1b7c997f.chunk.js
                                 2.38 kB    build/static/js/944.1c1c4844.chunk.js
                                 2.35 kB    build/static/js/339.aad914f3.chunk.js
                                 2.33 kB    build/static/js/180.772f40ff.chunk.js
                                 2.32 kB    build/static/js/345.0f05bfdb.chunk.js
                                 2.22 kB    build/static/js/435.5b0f49f5.chunk.js
                                 2.07 kB    build/static/js/694.aa920cb3.chunk.js
                                 2.06 kB    build/static/js/182.15eed613.chunk.js
                                 2.04 kB    build/static/js/891.659e25da.chunk.js
2022-06-02T09:27:54.969Z [INFO]: 2.02 kB    build/static/js/313.68b0a254.chunk.js
                                 1.87 kB    build/static/js/242.70fbec6a.chunk.js
                                 1.78 kB    build/static/js/791.20392bd0.chunk.js
                                 1.7 kB     build/static/js/945.73b44d1d.chunk.js
                                 1.49 kB    build/static/js/700.e59b34b2.chunk.js
                                 1.3 kB     build/static/js/379.7add2a32.chunk.js
                                 1.29 kB    build/static/js/176.81af69fd.chunk.js
                                 1.28 kB    build/static/js/790.5402e8fd.chunk.js
                                 1.23 kB    build/static/css/main.8ae4e198.css
                                 759 B      build/static/js/500.d141322f.chunk.js
                                 309 B      build/static/js/754.01824e8a.chunk.js
                                 304 B      build/static/js/483.1f79a3ae.chunk.js
                                 303 B      build/static/js/288.55e0fd15.chunk.js
2022-06-02T09:27:54.973Z [INFO]: The project was built assuming it is hosted at /.
2022-06-02T09:27:54.973Z [INFO]: You can control this with the homepage field in your package.json.
                                 The build folder is ready to be deployed.
                                 You may serve it with a static server:
                                 yarn global add serve
                                 serve -s build
                                 Find out more about deployment here:
                                 https://cra.link/deployment
2022-06-02T09:27:55.075Z [INFO]: Done in 59.69s.