Webpack 종속성 관리 가이드
게시 됨: 2022-03-11모듈화의 개념은 대부분의 현대 프로그래밍 언어에 내재된 부분입니다. 그러나 JavaScript는 최신 버전의 ECMAScript ES6이 출시될 때까지 모듈화에 대한 공식적인 접근 방식이 부족했습니다.
오늘날 가장 인기 있는 JavaScript 프레임워크 중 하나인 Node.js에서 모듈 번들러를 사용하면 웹 브라우저에서 NPM 모듈을 로드할 수 있으며 React와 같은 구성 요소 지향 라이브러리는 JavaScript 코드의 모듈화를 장려하고 촉진합니다.
Webpack은 JavaScript 코드와 스타일시트, 이미지, 글꼴과 같은 모든 정적 자산을 번들 파일로 처리하는 사용 가능한 모듈 번들러 중 하나입니다. 처리에는 컴파일, 연결, 축소 및 압축과 같은 코드 종속성을 관리하고 최적화하는 데 필요한 모든 작업이 포함될 수 있습니다.
그러나 Webpack 및 해당 종속성을 구성하는 것은 스트레스가 될 수 있으며 특히 초보자에게 항상 간단한 프로세스는 아닙니다.
이 블로그 게시물은 다양한 시나리오에 대해 Webpack을 구성하는 방법에 대한 지침과 예제를 제공하고 Webpack을 사용한 프로젝트 종속성 번들링과 관련된 가장 일반적인 함정을 지적합니다.
이 블로그 게시물의 첫 번째 부분에서는 프로젝트에서 종속성의 정의를 단순화하는 방법을 설명합니다. 다음으로 다중 및 단일 페이지 애플리케이션의 코드 분할을 위한 구성을 논의하고 시연합니다. 마지막으로 프로젝트에 타사 라이브러리를 포함하려는 경우 Webpack을 구성하는 방법에 대해 논의합니다.
별칭 및 상대 경로 구성
상대 경로는 종속성과 직접적인 관련이 없지만 종속성을 정의할 때 사용합니다. 프로젝트 파일 구조가 복잡한 경우 관련 모듈 경로를 확인하기 어려울 수 있습니다. Webpack 구성의 가장 기본적인 이점 중 하나는 프로젝트의 상대 경로 정의를 단순화하는 데 도움이 된다는 것입니다.
다음과 같은 프로젝트 구조가 있다고 가정해 보겠습니다.
- Project - node_modules - bower_modules - src - script - components - Modal.js - Navigation.js - containers - Home.js - Admin.js
필요한 파일에 대한 상대 경로로 종속성을 참조할 수 있으며 구성 요소를 소스 코드의 컨테이너로 가져오려면 다음과 같습니다.
Home.js
Import Modal from '../components/Modal'; Import Navigation from '../components/Navigation';
Modal.js
import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';
스크립트나 모듈을 가져올 때마다 현재 디렉터리의 위치를 알아야 하고 가져올 대상의 상대 경로를 찾아야 합니다. 중첩된 파일 구조가 있는 큰 프로젝트가 있거나 복잡한 프로젝트 구조의 일부를 리팩토링하려는 경우 이 문제가 어떻게 복잡해질 수 있는지 상상할 수 있습니다.
Webpack의 resolve.alias
옵션으로 이 문제를 쉽게 처리할 수 있습니다. 우리는 소위 별칭을 선언할 수 있습니다. 디렉토리 또는 모듈의 이름과 그 위치를 지정할 수 있으며 프로젝트 소스 코드의 상대 경로에 의존하지 않습니다.
webpack.config.js
resolve: { alias: { 'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), } }
Modal.js
파일에서 이제 훨씬 간단하게 datepicker를 가져올 수 있습니다.
import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';
코드 분할
최종 번들에 스크립트를 추가하거나 최종 번들을 분할해야 하거나 요청 시 별도의 번들을 로드해야 하는 시나리오가 있을 수 있습니다. 이러한 시나리오에 대한 프로젝트 및 Webpack 구성을 설정하는 것은 간단하지 않을 수 있습니다.
Webpack 구성에서 Entry
옵션은 Webpack에 최종 번들의 시작점이 어디인지 알려줍니다. 진입점은 String, Array 또는 Object의 세 가지 데이터 유형을 가질 수 있습니다.
단일 시작점이 있다면 이러한 형식 중 하나를 사용하여 동일한 결과를 얻을 수 있습니다.
여러 파일을 추가하고 서로 의존하지 않는 경우 배열 형식을 사용할 수 있습니다. 예를 들어, bundle.js
끝에 analytics.js
를 추가할 수 있습니다.
webpack.config.js
module.exports = { // creates a bundle out of index.js and then append analytics.js entry: ['./src/script/index.jsx', './src/script/analytics.js'], output: { path: './build', filename: bundle.js ' } };
여러 진입점 관리
index.html
및 admin.html
과 같은 여러 HTML 파일이 있는 다중 페이지 애플리케이션이 있다고 가정해 보겠습니다. 진입점을 객체 유형으로 사용하여 여러 번들을 생성할 수 있습니다. 아래 구성은 두 개의 JavaScript 번들을 생성합니다.
webpack.config.js
module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) } };
index.html
<script src=”build/index.js”></script>
admin.html
<script src=”build/admin.js”></script>
두 JavaScript 번들은 공통 라이브러리와 구성 요소를 공유할 수 있습니다. 이를 위해 여러 항목 청크에서 발생하는 모듈을 찾고 여러 페이지 간에 캐시될 수 있는 공유 번들을 생성하는 CommonsChunkPlugin
을 사용할 수 있습니다.
webpack.config.js
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) }, plugins: [commonsPlugin] };
이제 번들된 스크립트 앞에 <script src="build/common.js"></script>
를 추가하는 것을 잊지 말아야 합니다.
지연 로딩 활성화
Webpack은 정적 자산을 더 작은 청크로 분할할 수 있으며 이 접근 방식은 표준 연결보다 유연합니다. 큰 단일 페이지 애플리케이션(SPA)이 있는 경우 하나의 번들로 단순 연결하는 것은 좋은 접근 방식이 아닙니다. 하나의 거대한 번들을 로드하는 속도가 느릴 수 있고 사용자는 일반적으로 각 보기에 대한 모든 종속성이 필요하지 않기 때문입니다.
이전에 애플리케이션을 여러 번들로 분할하고, 공통 종속성을 연결하고, 브라우저 캐싱 동작을 활용하는 방법을 설명했습니다. 이 접근 방식은 다중 페이지 응용 프로그램에는 매우 잘 작동하지만 단일 페이지 응용 프로그램에는 적합하지 않습니다.
SPA의 경우 현재 보기를 렌더링하는 데 필요한 정적 자산만 제공해야 합니다. SPA 아키텍처의 클라이언트 측 라우터는 코드 분할을 처리하기에 완벽한 장소입니다. 사용자가 경로를 입력하면 결과 보기에 필요한 종속성만 로드할 수 있습니다. 또는 사용자가 페이지를 아래로 스크롤할 때 종속성을 로드할 수 있습니다.
이를 위해 Webpack이 정적으로 감지할 수 있는 require.ensure
또는 System.import
함수를 사용할 수 있습니다. Webpack은 이 분할 지점을 기반으로 별도의 번들을 생성하고 요청 시 호출할 수 있습니다.
이 예제에는 두 개의 React 컨테이너가 있습니다. 관리자 보기 및 대시보드 보기.
admin.jsx
import React, {Component} from 'react'; export default class Admin extends Component { render() { return <div > Admin < /div>; } }
dashboard.jsx
import React, {Component} from 'react'; export default class Dashboard extends Component { render() { return <div > Dashboard < /div>; } }
사용자가 /dashboard
또는 /admin
URL을 입력하면 해당하는 필수 JavaScript 번들만 로드됩니다. 아래에서 클라이언트 측 라우터가 있거나 없는 예를 볼 수 있습니다.
index.jsx
if (window.location.pathname === '/dashboard') { require.ensure([], function() { require('./containers/dashboard').default; }); } else if (window.location.pathname === '/admin') { require.ensure([], function() { require('./containers/admin').default; }); }
index.jsx
ReactDOM.render( <Router> <Route path="/" component={props => <div>{props.children}</div>}> <IndexRoute component={Home} /> <Route path="dashboard" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/dashboard').default) }, "dashboard")}} /> <Route path="admin" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/admin').default) }, "admin")}} /> </Route> </Router> , document.getElementById('content') );
스타일을 별도의 번들로 추출하기
Webpack에서 style-loader
및 css-loader
loader와 같은 로더는 스타일시트를 사전 처리하여 출력 JavaScript 번들에 포함하지만 경우에 따라 FOUC( 스타일 없는 콘텐츠의 Flash)를 유발할 수 있습니다.

모든 스타일을 최종 JavaScript 번들에 포함시키는 대신 별도의 CSS 번들로 생성할 수 있는 ExtractTextWebpackPlugin
으로 FOUC를 피할 수 있습니다.
webpack.config.js
var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { loaders: [{ test: /\.css/, loader: ExtractTextPlugin.extract('style', 'css')' }], }, plugins: [ // output extracted CSS to a file new ExtractTextPlugin('[name].[chunkhash].css') ] }
타사 라이브러리 및 플러그인 처리
동일한 구성 요소를 처음부터 개발하는 데 시간을 소비하고 싶지 않기 때문에 타사 라이브러리, 다양한 플러그인 또는 추가 스크립트를 사용해야 하는 경우가 많습니다. 적극적으로 유지 관리되지 않고 JavaScript 모듈을 이해하지 못하고 사전 정의된 이름으로 전 세계적으로 종속성이 있다고 가정하는 많은 레거시 라이브러리 및 플러그인을 사용할 수 있습니다.
다음은 최종 번들을 생성할 수 있도록 Webpack을 적절하게 구성하는 방법에 대한 설명과 함께 jQuery 플러그인의 몇 가지 예입니다.
제공 플러그인
대부분의 타사 플러그인은 특정 전역 종속성의 존재에 의존합니다. jQuery의 경우 플러그인은 정의되는 $
또는 jQuery
변수에 의존하며 우리 코드에서 $('div.content').pluginFunc()
를 호출하여 jQuery 플러그인을 사용할 수 있습니다.
Webpack 플러그인 ProvidePlugin
을 사용하여 전역 $
식별자를 만날 때마다 var $ = require("jquery")
를 추가할 수 있습니다.
webpack.config.js
webpack.ProvidePlugin({ '$': 'jquery', })
Webpack은 코드를 처리할 때 $
존재를 찾고 require
함수에서 지정한 모듈을 가져오지 않고 전역 종속성에 대한 참조를 제공합니다.
수입 로더
일부 jQuery 플러그인은 전역 네임스페이스에서 $
를 가정하거나 this
window
개체인 것에 의존합니다. 이를 위해 전역 변수를 모듈에 주입하는 imports-loader
를 사용할 수 있습니다.
example.js
$('div.content').pluginFunc();
그런 다음 imports-loader
를 구성하여 $
변수를 모듈에 주입할 수 있습니다.
require("imports?$=jquery!./example.js");
이것은 단순히 var $ = require("jquery");
example.js
로.
두 번째 사용 사례:
webpack.config.js
module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?jQuery=jquery,$=jquery,this=>window' }] }
=>
기호(ES6 Arrow 함수와 혼동하지 말 것)를 사용하여 임의의 변수를 설정할 수 있습니다. 마지막 값은 전역 변수 this
를 window
개체를 가리키도록 재정의합니다. 파일의 전체 내용을 (function () { ... }).call(window);
window
을 인수로 사용하여 this
함수를 호출합니다.
CommonJS 또는 AMD 모듈 형식을 사용하는 라이브러리를 요구할 수도 있습니다.
// CommonJS var $ = require("jquery"); // jquery is available // AMD define(['jquery'], function($) { // jquery is available });
일부 라이브러리 및 모듈은 다른 모듈 형식을 지원할 수 있습니다.
다음 예에는 AMD 및 CommonJS 모듈 형식을 사용하고 jQuery 종속성이 있는 jQuery 플러그인이 있습니다.
jquery-plugin.js
(function(factory) { if (typeof define === 'function' && define.amd) { // AMD format is used define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS format is used module.exports = factory(require('jquery')); } else { // Neither AMD nor CommonJS used. Use global variables. } });
webpack.config.js
module: { loaders: [{ test: /jquery-plugin/, loader: "imports?define=>false,exports=>false" }] }
특정 라이브러리에 사용할 모듈 형식을 선택할 수 있습니다. define
를 false
로 선언하면 Webpack은 AMD 모듈 형식으로 모듈을 구문 분석하지 않고 변수 exports
를 false
로 선언하면 Webpack은 CommonJS 모듈 형식으로 모듈을 구문 분석하지 않습니다.
노출 로더
모듈을 전역 컨텍스트에 노출해야 하는 경우 expose-loader
를 사용할 수 있습니다. 예를 들어 Webpack 구성의 일부가 아닌 외부 스크립트가 있고 전역 네임스페이스의 기호에 의존하는 경우 또는 브라우저 콘솔의 기호에 액세스해야 하는 브라우저 플러그인을 사용하는 경우에 유용할 수 있습니다.
webpack.config.js
module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }
jQuery 라이브러리는 이제 웹 페이지의 다른 스크립트에 대한 전역 네임스페이스에서 사용할 수 있습니다.
window.$ window.jQuery
외부 종속성 구성
외부 호스팅 스크립트의 모듈을 포함하려면 구성에서 모듈을 정의해야 합니다. 그렇지 않으면 Webpack은 최종 번들을 생성할 수 없습니다.
Webpack 구성에서 externals
옵션을 사용하여 외부 스크립트를 구성할 수 있습니다. 예를 들어 별도의 <script>
태그를 통해 CDN의 라이브러리를 사용할 수 있으며 여전히 프로젝트에서 모듈 종속성으로 명시적으로 선언합니다.
webpack.config.js
externals: { react: 'React', 'react-dom': 'ReactDOM' }
라이브러리의 여러 인스턴스 지원
타사 라이브러리 및 종속성을 관리하기 위해 프런트 엔드 개발에서 NPM 패키지 관리자를 사용하는 것이 좋습니다. 그러나 때때로 우리는 버전이 다른 동일한 라이브러리의 여러 인스턴스를 가질 수 있으며 한 환경에서 잘 작동하지 않습니다.
예를 들어 NPM에서 React를 설치할 수 있는 React 라이브러리에서 발생할 수 있으며 나중에 일부 추가 패키지 또는 플러그인을 통해 다른 버전의 React를 사용할 수 있습니다. 프로젝트 구조는 다음과 같습니다.
project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react
react-plugin
에서 오는 구성 요소는 프로젝트의 나머지 구성 요소와 다른 React 인스턴스를 갖습니다. 이제 React의 두 개의 개별 사본이 있으며 서로 다른 버전이 될 수 있습니다. 우리 애플리케이션에서 이 시나리오는 변경 가능한 전역 DOM을 엉망으로 만들 수 있으며 웹 콘솔 로그에서 오류 메시지를 볼 수 있습니다. 이 문제에 대한 해결책은 전체 프로젝트에서 동일한 버전의 React를 사용하는 것입니다. Webpack 별칭으로 해결할 수 있습니다.
webpack.config.js
module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }
react-plugin
이 React를 요구할 때 프로젝트의 node_modules
에 있는 버전을 사용합니다. 어떤 버전의 React를 사용하는지 알고 싶다면 소스 코드에 console.log(React.version)
를 추가하면 됩니다.
Webpack 구성이 아닌 개발에 집중
이 게시물은 Webpack의 강력함과 유용성에 대한 간략한 설명입니다.
JavaScript 번들링을 최적화하고 간소화하는 데 도움이 되는 다른 많은 Webpack 로더 및 플러그인이 있습니다.
초보자라도 이 가이드는 구성 번들링보다 개발에 더 집중할 수 있도록 Webpack 사용을 시작할 수 있는 견고한 기반을 제공합니다.