LINE Corporation이 2023년 10월 1일부로 LY Corporation이 되었습니다. LY Corporation의 새로운 기술 블로그를 소개합니다. LY Corporation Tech Blog

Blog


JavaScript SDK 성능개선 방법 - 압축과 최적화로 실행시간 단축하기

들어가며

안녕하세요. 저는 QUICK Game Platform Dev 팀에서 게임에서 필요한 여러 기능을 제공하는 QUICK Game SDK를 개발하고 있는 이재호입니다. QUICK Game Platform이란 HTML5 기반의 게임을 서비스하기 위해 LINE에서 제공하는 플랫폼입니다. 저는 이번 글에서 QUICK Game SDK를 개발하고 개선하면서 SDK의 크기를 줄이고 성능을 개선했던 사례를 공유하려고 합니다. 

환경

실험은 다음과 같은 환경에서 진행되었습니다.

Dependency 모듈명 버전
devDependencies webpack 4.28.2
devDependencies @babel/core 7.4.3
devDependencies @babel/cli 7.4.3
devDependencies babel-loader 7.1.5
devDependencies mini-css-extract-plugin 0.7.0
devDependencies webpack-bundle-analyzer 3.3.2

QUICK Game Platform SDK 성능 개선 결과

먼저 결과부터 말씀드리겠습니다. 아래는 본문에서 설명드릴 개선 방법을 QUICK Game Platform SDK에 적용한 후 각 지표별로 얼마나 개선되었는지 측정한 결과입니다. 

실험 환경에 따라 아래 실험 결과는 달라질 수 있으니 단순 참고용으로만 보시기 바랍니다.

지표 결과
SDK 코어(core) 모듈 로딩 시간 약 83% 감소
SDK 코어 모듈 파일 크기(청크(chunk) 처리한 파일 배제) 약 87% 감소
총 자원 로딩 시간(이미지 자원을 제외하고 SDK의 플러그인 모듈까지 모두 로드하는데 걸리는 시간) 약 31% 감소

성능 개선 방법 - 최소화(minimization)

1. 개발 모드 확인

webpack의 개발 모드가 'production' 모드인지 'development' 모드인지 확인합니다. production 모드에서 빌드하면 몇몇 모듈은 자동으로 최소화되어 크기를 줄이는 데 도움이 됩니다.

예시 코드

//webpack development mode.
process.env.BABEL_ENV = 'development'
process.env.NODE_ENV = 'development'
module.exports = {
    mode: 'development',
    ...others
}
 
 
//webpack production mode.
process.env.BABEL_ENV = 'production'
process.env.NODE_ENV = 'production'
module.exports = {
    mode: 'production',
    ...others
 
}

실험 결과

React 모듈을 각각의 모드로 빌드한 후 결과를 살펴보았습니다. webpack 분석 플러그인 화면에서 production 모드의 React 영역이 상당히 줄어든 걸 확인할 수 있습니다.

모드 파일 크기 webpack 분석 플러그인 화면
development js: 99KB,
gzipped: 34KB
production js: 82KB,
gzipped: 28KB

2. Tree shaking 개선

Tree Shaking이란 사용하지 않는 모듈을 제거한 후 빌드하는 것을 의미합니다. webpack에서는 빌드할 때 기본적으로 사용하지 않는 모듈을 제거하지만, 사용 여부가 모호한 모듈은 제거되지 않고 빌드됩니다. 아래 예시 코드의 TO-BE와 같이 임포트하면 사용하지 않는 서브 모듈이 포함되는 것을 막을 수 있습니다. 

예시 코드

//AS-IS
import moduleA from 'moduleA'
moduleA.get('')
 
 
//TO-BE
import moduleA from 'moduleA/get'
moduleA.get()

실험 결과

Lodash 모듈에서 사용하는 서브 모듈만 포함시켜 파일 크기가 줄어든 것을 확인할 수 있습니다.

모드 파일 크기 webpack 분석 플러그인 화면
Lodash 모듈 전체 임포트 js: 74KB,
gzipped: 26KB
Lodash 모듈에서 get만 임포트 js: 11KB,
gzipped: 3KB

3. 자원 외부화

자바스크립트로 개발할 때 코드와 함께 필요한 자원(asset)을 파일에 포함해야 하는 경우가 있습니다. 이때 자원을 Base64로 변환하여 같이 빌드하거나 외부로 빼내는 작업을 진행하는데요. 만약 자원의 크기가 크다면, 외부로 빼서 따로 두는 형태로 빌드한 후 CDN(contents delivery netword)에서 자원을 가져오게 하면 처음 자바스크립트 파일을 로드할 때 걸리는 시간을 줄일 수 있습니다. 다만 이때 주의해야 할 점이 있는데요. 자원의 크기가 별로 크지 않고 네트워크의 성능이 좋지 않다면 로드할 때 오히려 더 오랜 시간이 걸릴 수 있습니다.

예시 코드

//AS-IS
module.exports = {
    ...others,
    module: [
        ...otherModules,
        {
            test: /.(sa|sc|c)ss$/,
            use: ['style-loader', 'css-loader', 'sass-loader']
        },
        {
            test: /.(png|jpg|gif)$/,
            use: [
                {
                    loader: 'url-loader',
                }
            ]
        }
    ]
}
 
 
//TO-BE
module.exports = {
    ...others,
    module: [
        ...otherModules,
        {
            test: /.(sa|sc|c)ss$/,
            use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] // style-loader에서 MiniCssExtractPlugin.loader로 변경
        },
        {
            test: /.(png|jpg|gif)$/,
            use: [
                {
                    loader: 'file-loader', // url-loader에서 file-loader로 변경
                    options: {
                      emitFile: true,
                      name: '[name].[ext]',
                    },
 
            }]
        }
    ]
}

실험 결과

자원 외부화로 test.jpeg이 빠지면서 파일 크기가 많이 줄어든 걸 확인할 수 있습니다.

모드 파일 크기 webpack 분석 플러그인 화면
자원을 포함했을 때 js: 112KB,
gzipped: 47KB
자원을 포함하지 않았을 때 js: 82KB,
gzipped: 28KB, 
image: 23KB

4. 자원 파일 크기 축소

어떤 상황이나 환경에선 고화질의 이미지 파일보다 빠른 로딩 속도가 중요할 수 있습니다. 따라서 제공하는 자원의 크기를 각 상황에 맞게 최적화하면 사용자 경험에 좋은 영향을 줄 수 있습니다. 몇 가지 툴을 이용하면 이미지 파일의 크기를 쉽게 조절할 수 있는데요. 가장 쉽게 사용할 수 있는 툴로는 imageOptim이 있습니다. 

5. 필요 없는 모듈 제외 

서비스에 필요없는 몇 가지 모듈이 파일에 포함되는 경우가 있는데요. 특히 애플리케이션에서 특정 국가의 언어만 지원할 때는 moment.js에 포함된 그 외 국가들의 로캘(locale) 정보는 필요하지 않습니다. 이를 제거하면 파일 크기를 줄일 수 있습니다.

예시 코드

//webpack.config.js
module.exports = {
    ...others,
    plugins: [
        ...otherPlugins,
        new webpack.IgnorePlugin(/^./locale$/, /moment$/),
    ]
}

실험 결과

필요하지 않은 로캘 정보를 제외시킨 결과, 파일 크기가 많이 줄어든 것을 확인할 수 있습니다.

모드 파일 크기 webpack 분석 플러그인 화면
로캘을 모두 포함했을 때 js: 430KB,
gzipped: 115KB
필요한 로캘만 포함했을 때 js: 166KB,
gzipped: 65KB

6. 압축 파일 활용

일부 브라우저에서는 압축 파일 형식을 지원합니다. 따라서 gzip 형식의 파일을 함께 올려 자원을 선택적으로 내려 받을 수 있게 지원하는 것이 좋습니다.

예시 코드

//webpack.config.js
module.exports = {
    plugins: [
        ...otherPlugins,
        new CompressionPlugin(),
    ]
}

성능 개선 방법 - 최적화(optimization)

Dynamic Import와 모듈 chunk, Promise.all 조합

어떤 모듈은 처음 실행되는 시점에선 꼭 필요하지 않습니다. 또한 여러 리소스를 동시에 다운 받아 좋은 사용자 경험을 만들 수도 있습니다. 이런 점을 활용하여 Promise.all로 청크 처리한 모듈을 Dynamic Import하여 로딩에 걸리는 시간을 줄일 수 있습니다.

예시 코드

//webpack.config.js
module.exports = {
    module: {
        rules: [
            {
                test: /.js(x)?$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        plugins: [
                            ...others,
                            '@babel/plugin-syntax-dynamic-import',
                        ]
                    }
                }
            }
        ]
    }
}
 
 
//AS-IS
import m1 from './m1'
import m2 from './m2'
import m3 from './m3'
 
 
//TO-BE
return Promise.all([
  import(
    './module1'
    /* webpackPreload: true */
    /* webpackChunkName: "m1" */ )
    .then(({default: m1}) => m1),
  import(
    'module2'
    /* webpackPreload: true */
    /* webpackChunkName: "m2" */)
    .then(({ default: m2 }) => m2),
  import(
    'module3'
    /* webpackPreload: true */
    /* webpackChunkName: "m3" */)
  .then(({ default: m3 }) => m3)])
    .then(([m1, m2, m3]) => window.context = { m1, m2, m3 })

실험 결과

모듈을 청크 처리하면 각각에 해당하는 plugin.js 파일이 생성되는데요. 아래 webpack 분석 플러그인 화면에서 확인할 수 있습니다(서로 색깔이 다른 부분을 참조). 사실 처리 전후 전체 용량은 차이가 없습니다. 하지만 처음 실행할 때는 꼭 필요한 모듈만 불러오고, 그 외는 이후 필요할 때 불러오기 때문에 초기 로딩 시간을 단축시킬 수 있습니다.

모드 파일 크기 webpack 분석 플러그인 화면
모듈 청크 처리한 경우 core: 33KB, gzipped: 20KB
plugin1: 8KB, gzipped: 3KB
plugin2: 72KB, gzipped: 25KB
plugin3: 54KB, gzipped: 17KB
모듈 청크 처리하지 않은 경우 js: 166KB
gzipped : 65KB

마치며

지금까지 SDK 성능을 높이기 위한 몇 가지 방법과 이를 실제로 적용한 사례를 말씀드렸습니다. 이외에도 더 많은 방법이 있겠지만, 제가 직접 경험해 보고 쉽게 적용할 수 있겠다고 생각한 방법 위주로 말씀드렸습니다. 이런 방법을 적용하여 좀 더 성능이 개선된 여러 서비스가 나오기를 기대하고 있습니다. 단, 글 서두에 말씀드린 대로 모듈 버전과 빌드 방법에 따라서 각 방법을 적용하는 방식과 결과가 상이할 수 있습니다. 이점 참고하시기 바랍니다. 

혹시 본문에 제가 오기한 내용이 있다면 가감없는 의견을 여기로 전달해 주시면 감사하겠습니다.

참고자료