JavaScript 모듈화 도구, webpack

    728x90
    반응형
    SMALL

    ● JavaScript 모듈 시스템과 webpack

    ● 모듈 정의와 모듈 사용

    ● webpack 사용 방법

    ● 로더

    ● 컴파일 성능

    ● 개발자 도구 연동

    ● 빌드 도구 연동

    ● 정리

     

     

     

     

    서버에서 처리하는 로직을 JavaScript로 구현하는 부분이 많아지면서 웹 서비스 개발에서 JavaScript로 작성하는 코드의 양도 늘어났습니다. 코드의 양이 많아지면 코드의 유지와 보수가 쉽도록 코드를 모듈로 나누어 관리하는 모듈 시스템이 필요해집니다. 그러나 JavaScript는 언어 자체가 지원하는 모듈 시스템이 없습니다. 이런 한계를 극복하려 여러 가지 도구를 활용하는데 그 도구 가운데 하나가 webpack입니다.

     

    webpack은 모듈 시스템을 구성하는 기능 외에도 로더 사용, 빠른 컴파일 속도 등 장점이 많습니다.

    이 글에서는 webpack의 기능과 사용법을 간략하게 설명하고 webpack의 장점을 살펴보겠습니다.

     

    ● JavaScript 모듈 시스템과 webpack

    이전부터 JavaScript 모듈화 시스템을 적용하려는 많은 노력이 있었다.

    JavaScript 모듈화 명세를 만드는 대표적인 작업 그룹에 CommonJS와 AMD(Asynchronous Module Definition)가 있다.

    webpack은 두 그룹의 명세를 모두 지원하는 JavaScript모듈화 도구이다.

    조금 더 간결한 코드를 작성할 수 있는 CommonJS 명세로 작성한 예를 보며 webpack이 어떻게 JavaScript를 모듈화하는지 간략하게 살펴보겠다.

     

    ● 모듈 정의와 모듈 사용

     

     

    모듈을 작성하고 다음과 같이 module.exports 속성에 외부에 배포할 모듈을 전달해 모듈을 정의한다.
    
    module.exports = {message: 'webpack'};  
    
    
    
    모듈을 사용할 때는 모듈을 로딩하는 파일의 require() 함수에 로딩할 모듈의 경로를 전달한다. 
    다음 코드는 모듈을 정의하는 examplemodule.js 파일을 사용하는 예다.
    
    alert(require('./examplemodule).message'));  
    
    여러 모듈을 합치거나 중첩해서 모듈을 로딩할 수도 있다. 
    다음은 브라우저의 알림 메시지에 'HelloWorld'를 표시하는 예다. 
    'HelloWorld'를 구성하는 단어를 모듈로 분리한 다음 두 모듈을 합쳐서 사용한다.
    
    
    
    'Hello'라는 메시지를 모듈로 만든 hello.js 파일은 다음과 같다.
    
    module.exports = 'Hello';  
    
    
    
    'World'라는 메시지를 모듈로 만든 world.js 파일은 다음과 같다.
    
    module.exports = 'World';  
    
    
    
    두 모듈을 합쳐 'HelloWorld'를 만드는 모듈인 greeting.js 파일은 다음과 같다.
    
    var greeting = require('./hello') + require('./world');
    
    module.exports = greeting; 
    
    
    
    두 모듈을 합친 greeting 모듈을 로딩해 브라우저에서 
    경고 메시지가 나타나게 구현하는 app.js 파일은 다음과 같다.
    
    alert(require('./greeting'));  

     

    모듈을 정의하고 사용하도록 로딩하는 방법은 어렵지 않다.

    다만 모듈로 만든 파일은 바로 웹페이지에 넣어 브라우저에서 실행할 수 없다.

    webpack으로 컴파일해 브라우저에서 실행할 수 있는 형태로 바꿔야 한다. 

     

    ● webpack 사용 방법

    webpack은 Node.js가 설치된 환경에서 실행된다.

    Node.js를 사용하는 환경에서 webpack을 설치하고 모듈을 컴파일하는 방법은 다음과 같다.

     

    ○ 설치와 컴파일

    webpack은 다음과 같이 npm 명령어로 설치할 수 있다.

    npm install webpack -g

     

    webpack이 설치되면 다음 예와 같이 webpack { 엔트리 파일경로 } { 번들 파일 경로 } 형식으로 명령어를 실행해 모듈을 컴파일한다.

     

    webpack ./entry.js bundle.js

     

    엔트리 파일은 다음 그림과 같이 서로 의존 관계에 있는 다양한 모듈을 사용하는 시작점이 되는 파일이다. 

    번들 파일은 브라우저에서 실행할 수 있게 모듈을 컴파일하는 파일이다.

     

    webpack에서 컴파일은 엔트리 파일을 시작으로 의존 관계에 있는 모듈을 엮어서 하나의 번들 파일을 만드는 작업이다.

    JavaScript를 사용하는 HTML 코드에서는 컴파일 결과로 만들어진 번들 파일만 포함하면 된다.

     

    컴파일 과정

     

    엔트리 파일이 여러 개일 때는 엔트리 파일마다 번들 파일이 생성된다.

     

    엔트리 파일이 여러 개일 때의 컴파일 과정

     

    컴파일 명령어에 --watch 옵션을 사용하면 모듈 파일이 변경될 때마다 변경된 모듈을 자동으로 다시 컴파일한다.

     

    webpack --watch ./entry.js bundle.js

     

    ○ 모듈의 스코프

    컴파일 과정에서 모듈은 함수로 감싸진다.

    예를 들어 다음 코드에서 greeting변수는 전역 변수지만 webpack으로 모듈이 컴파일된 뒤에는 지역 변수가 된다.

     

    var greeting = require('./hello') + require('./world');
    
    module.exports = gretting;

     

    다음은 위의 모듈을 컴파일해서 생성된 번들 파일의 내용이다.

    모듈을 작성할 때 모듈의 변수가 전역 변수가 되는 것을 피하려 변수로 감쌀 필요가 없다.

     

    ...
    /* 1 */
    /***/ function(module, exports, __webpack_require__) {
        var greeting = __webpack_require__(2) + __webpack_require__(3);
        module.exports = greeting;
    
    /***/ },
    ...

     

    설정 파일 사용

    CLI(command line interface)로 webpack을 실행해 컴파일할 때 엔트리 파일이 많거나 옵션이 많으면 입력하기 불편하다.

    설정 파일을 만들어 컴파일하면 이런 불편을 줄일 수 있다.

     

    webpack 설정 파일을 기본 형태는 다음과 같다.

     

    module.exports = {
    	context: __dirname + '/app', // 모든 파일 폴더
        entry: {
        	app: './app.js'
        },
        output: {
        	path: __dirname + '/dist', // 번들 파일 폴더
            filename: '[name].bundle.js' // 번들 파일 이름 규칙
        }
    }

     

    위와 같은 형태로 webpack.config.js 파일을 작성해 저장한다.

    설정 파일이 있는 디렉터리에서 다음과 같이 간단하게 명령어를 내리면 컴파일을 실행한다.

     

    webpack
    

     

    --watch 옵션으로 변경 사항을 자동으로 반영하려 할 때도  다음과 같이 간단한 명령어만 입력하면 된다.

     

    webpack --watch

     

    webpack을 사용할 때는 다양한 설정을 함께 사용하는 경우가 대부분이라 설정 파일로 컴파일하는 사례가 많다.

    더 다양한 설정 옵션과 자세한 설명은 webpack문서의 'Configruation'에서 확인할 수 있다.

     

    ●옵션

    resolve 속도를 올려 주기위한 options

     entry: 

    - 웹팩이 파일을 읽어 들이기 시작하는 부분

     app이 객체의 키로 설정되어 있는데 이 부분 이름은 자유롭게 바꾸시면 됩니다. 
     키가 app이면 결과물이 app.js로 나오고, zero면 zero.js로 나옵니다.
     
    {
      entry: {
        app: '파일 경로',
        zero: '파일 경로',
      }
    }
    
    app.js, zero.js 두 개가 생성됩니다. 
    결과물로 여러 JS를 만들고 싶을 때 저렇게 구분해주면 됩니다.
    
    
    {
      entry: {
        app: ['a.js', 'b.js'],
      },
    }
    
     a.js랑 b.js가 한 파일로 엮여 app.js라는 결과물로 나옵니다. 
     이렇게 웹팩은 entry의 js 파일부터 시작해서 import, require 관계로 묶여진 
     다른 js, css, json까지 알아서 파악한 뒤 모두 entry에 기재된 키 개수만큼으로 묶어줍니다.
    
    js 파일 대신 npm 모듈들을 넣어도 됩니다.
    
    {
      entry: {
        vendor: ['@babel/polyfill', 'eventsource-polyfill', 'react', 'react-dom'],
        app: ['@babel/polyfill', 'eventsource-polyfill', './client.js'],
      },
    }
    
    이렇게 하면 각각의 엔트리가 polyfill들이 적용된 상태로 output으로 나옵니다. 
    IE 환경에서 최신 자바스크립트를 사용해 개발하고 싶다면 저 두 폴리필을 
    npm에서 다운 받은 후 저렇게 모든 엔트리에 넣어주셔야 합니다.
    
    참고로 @babel/polyfill은 deprecated되었으니 core-js와 regenerator-runtime으로 
    대체하는 것이 좋습니다. 또한 entry에 넣기보다는 
    ./client.js같은 파일 최상단에 import나 require하는 것이 좋습니다.
    
    npm i core-js regenerator-runtime;
    
    import "core-js/stable";
    import "regenerator-runtime/runtime";
    
    밑에 loader에서 설명하겠지만
    @babel/preset-env에서 useBuiltIns: 'entry'를 하면 
    import한 것들이 target 속성에 맞춰 자동으로 변환됩니다.

     

     

     resolve.modules : 

    - 웹팩에게 module을 resolve할 때, 어떤 디렉토리를 찾아야 하는지 알려준다.

    - 절대 경로와 상대 경로 둘 다 사용되는데, 두 경로가 약간 다르게 행동한다.

    - 상대경로는 Node가 node_modules를 스캔하는 방식과 비슷하다. 현재 디렉토리의 nodemodule을 뒤지고 없으면 그 위의 nodemodule.. 루트까지 올라간다.

    - 절대 경로는 주어진 절대경로만 검색한다.

     

     resolve.extensions : 자동으로 특정 확장자만 resolve한다.

    module.exports = {
      //...
      resolve: {
        modules: [path.resolve(__dirname, 'src'), 'node_modules']
        extensions: ['.js', '.jsx']// , .scss ,.css]
      }
    };

     

    Target: string || [string] || false

    - webpack은 여러환경 또는 대상에 대해 컴파일 할 수 있습니다.

    - 기본값은 'browserlist' 또는 'web'으로 설정됩니다.

     

    async-node[[X].Y] Node.js와 같은 환경에서 사용하기 위해 컴파일 ( 비동기 적으로 청크 사용 fs및 vm로드)
    electron[[X].Y]-main 메인 프로세스를 위해 Electron 용으로 컴파일 합니다.
    electron[[X].Y]-renderer 대한 컴파일 전자 , 렌더러 프로세스를 사용하여 대상을 제공 JsonpTemplatePlugin, FunctionModulePlugin브라우저 환경과 NodeTargetPlugin및 ExternalsPluginCommonJS 및 전자에 대한 내장 모듈.
    electron[[X].Y]-preload 대한 컴파일 전자 를 사용하여 대상을 제공 렌더러 프로세스 NodeTemplatePlugin와 asyncChunkLoading로 설정 true, FunctionModulePlugin브라우저 환경과 NodeTargetPlugin및 ExternalsPluginCommonJS 및 전자에 대한 내장 모듈.
    node[[X].Y] Node.js와 같은 환경에서 사용하기 위해 컴파일 (Node.js require를 사용 하여 청크로드)
    node-webkit[[X].Y] WebKit에서 사용하기 위해 컴파일하고 청크로드를 위해 JSONP를 사용합니다. 내장 Node.js 모듈 가져 오기 및 nw.gui(실험적) 허용
    nwjs[[X].Y] 동일 node-webkit
    web 브라우저와 같은 환경에서 사용하기 위해 컴파일 (기본값)
    webworker WebWorker로 컴파일
    esX 지정된 ECMAScript 버전에 맞게 컴파일합니다. 예 : es5, es2020.
    browserslist browserslist-config에서 플랫폼과 ES 기능을 추론합니다 (browserslist 구성을 사용할 수있는 경우 기본값).

     

     Devtool : string = 'eval' || false

    이 옵션은 소스 맵 생성 여부와 방법을 제어합니다.

    플러그인을 따로 사용하여 처리할 수도 있다.

     

    devtool build rebuild production quility
    (none) fastest fastest yes bundled code
    eval fastest fastest no generated code
    eval-cheap-source-map fast faster no transformed code (lines only)
    eval-cheap-module-source-map slow faster no original source (lines only)
    eval-source-map slowest fast no original source
    eval-nosources-source-map - - -  
    eval-nosources-cheap-source-map - - -  
    eval-nosources-cheap-module-source-map - - -  
    cheap-source-map fast slow yes transformed code (lines only)
    cheap-module-source-map slow slower yes original source (lines only)
    inline-cheap-source-map fast slow no transformed code (lines only)
    inline-cheap-module-source-map slow slower no original source (lines only)
    inline-source-map slowest slowest no original source
    inline-nosources-source-map - - -  
    inline-nosources-cheap-source-map - - -  
    inline-nosources-cheap-module-source-map - - -  
    source-map slowest slowest yes original source
    hidden-source-map slowest slowest yes original source
    hidden-nosources-source-map - - -  
    hidden-nosources-cheap-source-map - - -  
    hidden-nosources-cheap-module-source-map - - -  
    hidden-cheap-source-map - - -  
    hidden-cheap-module-source-map - - -  
    nosources-source-map slowest slowest yes without source content
    nosources-cheap-source-map - - -  
    nosources-cheap-module-source-map        

     

     

     

    ● 로더

    webpack의 로더는 다양한 리소스를 JavaScript에서 바로 사용할 수 있는 형태로 로딩하는 기능이다.

    로더는 webpack의 특징적인 기능이면서 webpack을 강력한 도구로 만드는 기능이다.

     

    다음 그림과 같이 로더의 종류에 따라 JavaScript에서 사용할 수 있는 다양한 형태의 결과를 얻을 수 있다.

     

     

    로더 종류와 반환되는 결과

     

    템플릿 라이브러리인 handlebars를 로딩하는 로더인 handlebars-loader를 사용하는 예를 보며 로더를 사용하는 방법을 알아보겠따.

     

    handlebars 라이브러리가 설치된 환경에서 다음과 같이 npm 명령어를 실행해 handlebars-loader를 설치한다.

     

    npm install handlebars-loader

     

    로더가 설치되면 webpack.config.js 파일에 다음과 같이 로더 관련 설정을 추가한다.

     

    module.exports = {
    	...
        output: {
        	...
        },
        module: {
        	loaders: [
            // 적용할 파일의 패턴 (정규표현식)과 적용할 로더 설정
            {
            	test : /\-tpl.html$/,
                loader: 'handlebars'
            }
            ]
        }
    }

     

    사용할 템플릿 파일(example-tpl.html)의 예는 다음과 같다.

     

    <div>{{greeting}}</div>  

     

    템플릿을 사용하는 모듈의 내용은 다음과 같이 작성할 수 있다. require() 함수로 템플릿 파일을 로딩한 결과는 handlebars.compile() 함수를 거쳐 반환된 결과라 바로 데이터를 주입해 데이터와 결합된 HTML 코드를 얻을 수 있다.

     

    var listTpl = require('./example-tpl.html');  
    listTpl( { greeting: 'Hello World' } );  

     

    handlebars-loader를 활용하면 템플릿 파일을 별도의 HTML 파일로 관리할 수 있어 유지와 보수가 쉬워진다.

     

    webpack의 로더는 handlebars 외에 많은 라이브러리를 지원하기 때문에 활용 범위가 매우 넓은 기능이다.

    로더를 활용하면, Facebook의 라이브러리인 React의 JSX 형식도 사용할 수 있고, ECMAScript2015를 사용할 수 있게 컴파일하는 Babel도 사용할 수 있다.

    webpack이 지원하는 로더는 webpack 문서의 'list of loaders'에서 확인할 수 있다. 

     

     

    ● 컴파일 성능

    webpack을 사용하면 코드를 수정할 때마다 컴파일을 실행해야 수정한 코드가 적용된 결과를 바로바로 확인할 수 있다.

    --watch 옵션을 적용하면 변경된 내용이 있을 때마다 자동으로 컴파일할 수 있다. 하지만 작성하는 JavaScript 파일의 개수가 많은 실제 프로젝트에서 --watch 옵션을 적용한 webpack의 컴파일 성능이 얼마나 좋을지 의심됐다.

     

    현재 사용 중인 grunt-contrib-concat와 컴파일 성능을 비교해 봤다.

    테스트 대상 파일의 개수는 48개, 전체 코드의 양은 약 10,194줄이다.

    20회씩 컴파일한 결과는 다음과 같다.

     

    webpack과 grunt-contrib-concat의 컴파일 성능 비교 결과

     

    webpack의 20회 평균 컴파일 시간은 163ms, grunt-contrib-concat의 20회 평균 컴파일 시간은 819ms.

    테스트 전에는 단순이 파일을 병합하기만 하는 grunt-contrib-concat의 컴파일 속도가 더 빠를 것이라고 예상.

    하지만 webpack의 컴파일 속도가 더 빨랐다.

    webpack 공식 문서의 내용을 따르면 webpack이 비동기 I/O와 다중 캐시 레벨을 사용하기 때문에 컴파일 속도가 매우 빠르다고 한다.

     

    webpack uses async I/O and has multiple caching levels. 
    This makes webpack fast and incredibly fast on incremental compilation.

     

     

    ● 개발자 도구 연동

    webpack을 사용할 때 브라우저에서 실행되는 코드는 실제 작성한 코드가 아니라 webpack으로 컴파일된 코드다.

    모듈 시스템 적용을 위한 부가적인 코드 외에는 작성할 때의 코드가 거의 그대로 반영되기 때문에 디버깅하는 데 어려움은없다.

    하지만 컴파일 이후에는 엔트리 파일의 개수에 따라 하나하나 여러개의 파일로 모듈이 병합되기 때문에 코드를 작성할 때의 파일 구조를 파악해서 디버깅해야 할 때는 어려움이 있을 수 있다.

     

    webpack의 소스 맵 설정을 사용하면 컴파일된 파일에서도 원래의 파일 구조를 확인할 수 있다.

    설정 파일 (webpack.config.js) 에 다음과 같이 한 줄을 추가하고 컴파일한다.

     

    module.exports = {
    	...
        devtool: '#inline-source-map'
    }

     

    브라우저에서 웹 페이지를 다시 열고 개발자 도구를 실행하면 코드를 작성할 때의 파일 구조를 볼 수 있고 실제 작성한 코드에 접근할 수 있다.

    다음 그림은 Chrome의 개발자 도구를 실행한 화면이다.

    webpack:// 도메인 아래에 모듈을 구성하는 파일의 구조가 나타난다.

     

    Chrome 개발자 도구에서 확인한 webpack 컴파일 이전의 파일 구조

     

    Firefox에서는 Firebug를 통해 webpack의 소스 맵 기능을 사용할 수 있다.

     

    Firebug에서 webpack 컴파일 이전의 파일 확인

     

    ● 빌드 도구 연동

    webpack은 빌드 도구에서 사용할 수 있는 플러그인도 제공한다. 널리 쓰는 빌드 도구인 Grunt Gulp를 위한 webpack 플러그인은 다음과 같다.

    webpack이 제공하는 빌드 기능(압축, 난독화, 소스 파일 병합)만으로도 충분하다면 빌드 도구와 연동하지 않고 webpack만 단독으로 사용해도 된다.

     

     

    ● 정리

    webpack을 사용해 JavaScript를 모듈로 관리하는 방법을 간략하게 살펴봤다.

    webpack을 사용하며 느낀 장점들을 정리하면

     

    - 편리한 모듈 의존성 관리

     

    - 로더를 활용한 다양한 리소스의 효율적인 활용

     

    - 빠른 컴파일 속도

     

    - 잘 정리된 문서 (http://webpack.github.io/docs/)

     

    특히 로더는 다양한 리소스와 함께 개발하는 JavaScript 개발 환경에서 활용 범위가 매우 넓다고 할 수 있다.

     

    webpack은 Airbnb, Flipboard, Pinterest 등 실제 여러 회사에서 사용하고 있다(더 많은 사용 사례는 http://stackshare.io/webpack를 참고한다). 더 나은 JavaScript 개발 환경에 대해 고민하고 있는 분들께 이 글이 조금이나마 도움이 되면 좋겠다.

     

     

    참고:

     

    webpack.js.org/configuration/output/#outputlibrary

     

    Output | webpack

    webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.

    webpack.js.org

    d2.naver.com/helloworld/0239818

     

    728x90
    반응형
    LIST

    댓글