webpack은 모던 JavaScript 애플리케이션을 위한 정적 모듈 번들러 입니다. webpack이 애플리케이션을 처리할 때, 내부적으로는 프로젝트에 필요한 모든 모듈을 매핑하고 하나 이상의 번들을 생성하는 디펜던시 그래프를 만듭니다.
webpack 버전 4.0.0 이후로는 프로젝트를 번들링하기 위한 설정 파일을 필요로 하지 않습니다. 하지만 사용자 요구에 따라 기대 이상으로 유연하게 설정이 가능 합니다.
다음의 핵심 개념만 이해하면 시작할 수 있습니다.
이 문서에는 위 개념의 대략적인 개요를 제공하고, 개념별로 최적화된 사용 사례를 확인할 수 있는 링크를 제공하기 위한 목적으로 작성되었습니다.
모듈 번들러의 개념과 내부에서 동작하는 방식을 더 잘 이해하려면 다음 자료를 참고하세요.
엔트리 포인트는 webpack이 내부의 디펜던시 그래프 를 생성하기 위해 사용해야 하는 모듈입니다. Webpack은 엔트리 포인트가 (직간접적으로) 의존하는 다른 모듈과 라이브러리를 찾아냅니다.
기본값은 ./src/index.js
이지만, webpack 설정에서 entry
속성을 설정하여 다른 (또는 여러 엔트리 포인트)를 지정할 수 있습니다. 예를 들어보겠습니다.
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js',
};
output 속성은 생성된 번들을 내보낼 위치와 이 파일의 이름을 지정하는 방법을 webpack에 알려주는 역할을 합니다. 기본 출력 파일의 경우에는 ./dist/main.js
로 , 생성된 기타 파일의 경우에는 ./dist
폴더로 설정됩니다.
다음과 같이 설정에서 output
필드를 지정할 수 있습니다.
webpack.config.js
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
};
위의 예제에서, output.filename
과 output.path
속성을 사용하여 webpack에 번들의 이름과 내보낼 위치를 알려주었습니다. 상단에서 가져오는 path 모듈이 무엇인지 궁금할 수 있는데, 이것은 파일 경로를 지정하기 위해 사용되는 core Node.js 모듈입니다.
webpack은 기본적으로 JavaScript와 JSON 파일만 이해합니다. 로더를 사용하면 webpack이 다른 유형의 파일을 처리하거나, 그들을 유효한 모듈로 변환 하여 애플리케이션에서 사용하거나 디펜던시 그래프에 추가합니다.
상위 수준에서 로더는 webpack 설정에 두 가지 속성을 가집니다.
test
속성use
속성webpack.config.js
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
};
위 설정에서는 test
와 use
라는 두 가지 필수 속성을 가진 하나의 모듈을 위해 rules
속성을 정의했습니다. 이는 webpack의 컴파일러에 다음과 같이 말합니다.
"이봐 webpack 컴파일러,
require ()
/import
문 내에서 '.txt' 파일로 확인되는 경로를 발견하면 번들에 추가하기 전에raw-loader
를 사용하여 변환해."
더 상세한 사용자 지정 설정에 대해서는 로더에서 확인하실 수 있습니다.
로더는 특정 유형의 모듈을 변환하는 데 사용되지만, 플러그인을 활용하여 번들을 최적화하거나, 애셋을 관리하고, 또 환경 변수 주입등과 같은 광범위한 작업을 수행 할 수 있습니다.
플러그인을 사용하려면 require ()
를 통해 플러그인을 요청하고 plugins
배열에 추가해야 합니다. 대부분의 플러그인은 옵션을 통해 사용자가 지정할 수 있습니다. 다른 목적으로 플러그인을 여러 번 사용하도록 설정할 수 있으므로 new
연산자로 호출하여 플러그인의 인스턴스를 만들어야 합니다.
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 내장 plugin에 접근하는 데 사용
module.exports = {
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};
위의 예제에서 html-webpack-plugin
은 생성된 모든 번들을 자동으로 삽입하여 애플리케이션용 HTML 파일을 생성합니다.
webpack 설정에서 플러그인을 사용하는 것은 간단하지만, 추가로 실제 사용 사례들도 살펴볼만한 가치가 있습니다. 여기에서 자세히 알아보세요.
mode
파라미터를 development
, production
또는 none
으로 설정하면 webpack에 내장된 환경별 최적화를 활성화 할 수 있습니다. 기본값은 production
입니다.
module.exports = {
mode: 'production',
};
여기에서 모드 구성에 대해 자세히 알아보고 각 값에서 어떤 최적화가 수행되는지 알아보세요.
Webpack은 ES5가 호환되는 모든 브라우저를 지원합니다(IE8 이하는 지원되지 않습니다). Webpack은 import()
및 require.ensure()
을 위한 Promise
를 요구합니다. 구형 브라우저를 지원하려면 이러한 표현식을 사용하기 전에 폴리필을 로드해야 합니다.
Webpack 5는 Node.js 버전 10.13.0 이상에서 실행됩니다.
시작하기에서 언급한 바와 같이, webpack 설정에서 entry
속성을 정의하는 방법은 여러 가지가 있습니다. entry
속성을 설정 할 수 있는 방법과 유용한 이유를 설명하겠습니다.
Usage: entry: string | [string]
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js',
};
entry
속성에 대한 단일 엔트리 구문은 다음 내용의 축약된 표현입니다.
webpack.config.js
module.exports = {
entry: {
main: './path/to/my/entry/file.js',
},
};
"다중-메인 엔트리"로 알려진 것을 생성하기 위해 entry
속성에 파일 경로를 배열로 전달할 수 있습니다. 이는 여러 의존성 파일을 한 번에 주입하고 해당 의존성을 하나의 "청크"에 그래프로 표시하려는 경우에 유용합니다.
webpack.config.js
module.exports = {
entry: ['./src/file_1.js', './src/file_2.js'],
output: {
filename: 'bundle.js',
},
};
단일 엔트리 구문은 라이브러리같이 하나의 엔트리 포인트가 있는 애플리케이션 또는 도구에 대한 webpack 설정을 빠르게 설정하려는 경우 훌륭한 선택입니다. 그러나, 이 구문을 사용하면 설정을 확장할 수 있는 유연성이 떨어지게 됩니다.
Usage: entry: { <entryChunkName> string | [string] } | {}
webpack.config.js
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js',
},
};
객체 구문은 더 장황합니다. 그러나, 이것은 애플리케이션에서 엔트리를 정의하는 가장 확장 가능한 방법입니다.
엔트리 포인트 설명이 있는 객체입니다. 다음 속성을 지정할 수 있습니다.
dependOn
: 현재 엔트리 포인트가 의존하는 엔트리 포인트. 이 엔트리 포인트를 로드하기 전에 로드해야 합니다.
filename
: 디스크에 있는 각 출력 파일의 이름을 지정합니다.
import
: 시작 시 로드되는 모듈입니다.
library
: 현재 엔트리에서 라이브러리를 번들링하려면 라이브러리 옵션을 지정합니다.
runtime
: 런타임 청크의 이름입니다. 설정되면 이 이름의 런타임 청크가 생성되고 그렇지 않으면 기존 엔트리 포인트의 이름이 사용됩니다.
publicPath
: 브라우저에서 참조할 때 이 엔트리의 출력 파일에 대한 공용 URL 주소를 지정하세요. 또한 output.publicPath도 참고하세요.
webpack.config.js
module.exports = {
entry: {
a2: 'dependingfile.js',
b2: {
dependOn: 'a2',
import: './src/app.js',
},
},
};
runtime
과 dependOn
은 단일 엔트리에 함께 사용해서는 안되므로, 다음 설정은 유효하지 않으며 오류가 발생합니다.
webpack.config.js
module.exports = {
entry: {
a2: './a',
b2: {
runtime: 'x2',
dependOn: 'a2',
import: './b',
},
},
};
runtime
이 기존의 엔트리 포인트 이름을 가리키지 않아야 합니다. 예를 들어 아래 설정에서는 오류가 발생합니다.
module.exports = {
entry: {
a1: './a',
b1: {
runtime: 'a1',
import: './b',
},
},
};
또한 dependOn
은 순환이 아니어야 하며, 다음 예제에서는 다시 오류가 발생합니다.
module.exports = {
entry: {
a3: {
import: './a',
dependOn: 'b3',
},
b3: {
import: './b',
dependOn: 'a3',
},
},
};
다음은 엔트리 설정 및 실제 사용 사례 목록입니다.
webpack.config.js
module.exports = {
entry: {
main: './src/app.js',
vendor: './src/vendor.js',
},
};
webpack.prod.js
module.exports = {
output: {
filename: '[name].[contenthash].bundle.js',
},
};
webpack.dev.js
module.exports = {
output: {
filename: '[name].bundle.js',
},
};
이것은 무엇을 하나요? 위의 예와 같이 2개의 개별 엔트리 포인트를 원한다고 webpack에게 알려줍니다.
왜? 이를 통해, vendor.js
내에서 수정되지 않은 필수 라이브러리 또는 파일(예. Bootstrap, jQuery, images, 등)을 가져올 수 있으며, 이들은 자체 청크로 함께 번들 제공됩니다. 콘텐츠 해시는 동일하게 유지되므로, 브라우저가 별도로 캐시하여 로드 시간을 줄일 수 있습니다.
webpack.config.js
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js',
},
};
이것은 무엇을 하나요? 위의 예와 같이 3개의 개별 디펜던시 그래프를 원한다고 webpack에게 알려줍니다.
왜? 멀티 페이지 애플리케이션에서 서버가 새 HTML 문서를 가져옵니다. 페이지가 새 문서를 다시 로드하고 애셋이 다시 다운로드됩니다. 그러나, 이것은 optimization.splitChunks
를 사용하여 각 페이지 간에 공유되는 애플리케이션 코드 번들을 만드는 것과 같은 작업을 수행할 특별한 기회를 제공합니다. 엔트리 포인트들 사이에 코드/모듈을 많이 재사용하는 멀티 페이지 애플리케이션은 엔트리 포인트 수가 증가함에 따라 이런 기법의 혜택을 크게 얻을 수 있습니다.
output
옵션을 설정하여 컴파일된 파일을 디스크에 쓰는 방법을 webpack에 알려줍니다. 여러 진입
점이 있을 수 있지만 하나의 출력
설정만 지정된다는 점에 주의하세요.
webpack 설정의 output
프로퍼티는 최소한 객체로 값을 설정해야 하며, 출력 파일에 사용할 output.filename
이 제공되어야 합니다.
webpack.config.js
module.exports = {
output: {
filename: 'bundle.js',
},
};
이 설정은 단일 bundle.js
파일을 dist
디렉터리에 출력합니다.
설정에서 하나 이상의 "청크"를 생성하면(다중 엔트리 포인트나 CommonsChunkPlugin과 같은 플러그인을 사용하는 경우) substitution을 사용하여 파일이 고유한 이름을 갖도록 해야 합니다.
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js',
},
output: {
filename: '[name].js',
path: __dirname + '/dist',
},
};
// writes to disk: ./dist/app.js, ./dist/search.js
다음은 애셋에서 CDN과 해시를 사용한 조금 더 복잡한 예제입니다.
config.js
module.exports = {
//...
output: {
path: '/home/proj/cdn/assets/[fullhash]',
publicPath: 'https://cdn.example.com/assets/[fullhash]/',
},
};
출력 파일의 최종 publicPath
를 컴파일 시점에 알 수 없는 경우, 공백으로 남겨두고 런타임에 엔트리 포인트 파일의 __webpack_public_path__
를 통해 동적으로 설정할 수 있습니다.
__webpack_public_path__ = myRuntimePublicPath;
// rest of your application entry
로더는 모듈의 소스 코드에 변경 사항을 적용합니다. 파일을 import
하거나 “로드”할 때 전처리를 할 수 있습니다. 따라서 로더는 다른 빌드 도구의 “태스크”와 유사하며 프런트엔드 빌드 단계를 처리하는 강력한 방법을 제공합니다. 로더는 파일을 TypeScript와 같은 다른 언어에서 JavaScript로 변환하거나 인라인 이미지를 데이터 URL로 로드 할 수 있습니다. 로더를 사용하면 JavaScript 모듈에서 직접 CSS 파일을 import
하는 작업도 수행 할 수 있습니다!
예를 들어 로더를 사용하여 webpack에 CSS 파일을 로드하거나 TypeScript를 JavaScript로 변환할 수 있습니다. 이를 위해서 필요한 로더를 설치해봅시다.
npm install --save-dev css-loader ts-loader
그리고 모든.css
파일에 css-loader
를 사용하고, .ts
파일에는 ts-loader
를 사용하도록 webpack에 지시합니다.
webpack.config.js
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.ts$/, use: 'ts-loader' },
],
},
};
애플리케이션에서 로더를 사용하는 두 가지 방법이 있습니다.
로더는 webpack v4의 CLI에서 사용할 수 있지만, 이 기능은 webpack v5에서 더 이상 사용되지 않습니다.
module.rules
를 사용하면 webpack 설정 내에 여러 개의 로더를 지정할 수 있습니다.
이것은 로더를 표시하는 간결한 방법이며, 클린 코드를 유지하는 데 도움이 됩니다. 또한 각각의 로더에 대한 전체 개요를 제공합니다.
로더는 오른쪽에서 왼쪽으로 (또는 아래에서 위로) 평가/실행됩니다. 아래 예제에서는 sass-loader로 실행이 시작되고, css-loader로 이어지며 마지막으로 style-loader로 끝납니다. 로더 순서에 대한 더 자세한 내용은 "로더 기능"을 참고하세요.
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true,
},
},
{ loader: 'sass-loader' },
],
},
],
},
};
import
문이나 그와 동등한 "importing" 메소드에서 로더를 지정할 수 있습니다. 접두사 !
로 리소스에서 로더를 분리하세요. 각 부분은 현재 디렉터리를 기준으로 해석됩니다.
import Styles from 'style-loader!css-loader?modules!./styles.css';
configuration에서 인라인 import
문 앞에 접두사를 붙여 모든 로더, 프리 로더 및 포스트 로더를 재정의할 수 있습니다.
접두사 !
는 구성된 모든 일반 로더를 비활성화합니다.
import Styles from '!style-loader!css-loader?modules!./styles.css';
접두사 !!
는 구성된 모든 로더(프리 로더, 로더, 포스트 로더)를 비활성화합니다.
import Styles from '!!style-loader!css-loader?modules!./styles.css';
접두사 -!
는 포스트 로더를 제외한 구성된 모든 프리 로더와 로더를 비활성화합니다.
import Styles from '-!style-loader!css-loader?modules!./styles.css';
옵션은 쿼리 파라미터(예: ?key=value&foo=bar
) 또는 JSON 객체(예: ? { "key": "value", "foo": "bar"}
)로 전달할 수 있습니다.
options
객체로 구성 할 수 있습니다. (여전히 query
파라미터를 사용하여 옵션을 설정할 수 있지만 권장하지 않음)loader
필드가 있는 package.json
을 통해 main
및 로더를 내보낼 수 있습니다.로더는 전처리 기능을 통해 결과물을 커스터마이즈 할 수 있도록 합니다. 이제 사용자는 압축, 패키징, 언어 번역 뿐만 아니라 더 많은 세밀한 로직을 유연하게 추가할 수 있습니다.
로더는 표준 모듈 해석을 따릅니다. 대부분의 경우 모듈 경로에서 로드됩니다 (npm install
,node_modules
를 생각해보세요).
로더 모듈은 함수를 내보내고 Node.js와 호환되는 JavaScript로 작성해야 합니다. 로더 모듈은 일반적으로 npm으로 관리하지만, 커스텀 로더를 애플리케이션 내에 파일로 포함할 수도 있습니다. 컨벤션에 따르면 로더는 일반적으로 xxx-loader
(예 :json-loader
)로 명명합니다. 더 자세한 내용은 "로더 작성하기"를 참고하세요.
플러그인은 webpack의 기본입니다. Webpack 자체는 webpack 설정에서 사용하는 것과 동일한 플러그인 시스템으로 구축되어 있습니다.
로더가 할 수 없는 다른 작업을 수행할 목적으로 제공됩니다.
webpack 플러그인은 apply
메서드를 가지고 있는 객체입니다. apply
메서드는 webpack 컴파일러에 의해 호출되며, 전체 컴파일 라이프사이클에 접근 할 수 있습니다.
ConsoleLogOnBuildWebpackPlugin.js
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, (compilation) => {
console.log('The webpack build process is starting!');
});
}
}
module.exports = ConsoleLogOnBuildWebpackPlugin;
컴파일러 훅의 탭 메서드의 첫 번째 파라미터는 플러그인 이름의 카멜화된 버전으로 작성하는 것이 좋습니다. 모든 훅에서 재사용 할 수 있도록 하기 위해서 상수를 사용하는 것이 좋습니다.
플러그인은 매개변수 및 옵션을 사용할 수 있으므로, webpack 설정에서 plugins
속성에 새로운
인스턴스로 전달해야 합니다.
webpack을 사용하는 방법에 따라 여러 가지 방법으로 플러그인을 사용 할 수 있습니다.
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); //빌트인 플러그인에 접근하기 위함
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
},
],
},
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
};
ProgressPlugin
은 컴파일하는 동안 리포트를 사용자 정의로 생성 할 수 있으며 HtmlWebpackPlugin
은 script
를 사용하여 my-first-webpack.bundle.js
파일을 포함하는 HTML 파일을 생성합니다.
Node API를 사용할 때 설정의 플러그인
속성을 통해 플러그인을 전달 할 수도 있습니다.
some-node-script.js
const webpack = require('webpack'); //webpack 런타임에 접근하기 위함
const configuration = require('./webpack.config.js');
let compiler = webpack(configuration);
new webpack.ProgressPlugin().apply(compiler);
compiler.run(function (err, stats) {
// ...
});
webpack 설정이 정확히 동일한 경우는 거의 없습니다. 이것은 webpack의 설정 파일이 webpack 설정을 내보내는 JavaScript 파일이기 때문입니다. 설정은 정의된 속성에 따라 webpack에서 처리됩니다.
webpack은 표준 Node.js CommonJS 모듈이므로, 다음과 같은 작업을 할 수 있습니다.
require(...)
를 통해 다른 파일 가져오기require(...)
를 통해 npm 유틸리티 사용하기?:
연산자 같은 JavaScript 제어 흐름 표현식 사용하기상황에 따라 적절한 기능을 사용하세요.
기술적으로는 가능하지만, 아래 방법의 사용은 피해야 합니다.
--env
를 사용하는 대신, CLI 인자에 접근하기아래의 예제는 webpack 설정이 코드이기 때문에 표현적이면서 설정 가능하다는 것을 설명합니다.
webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js',
},
};
지원하는 모든 설정 옵션에 대해서는 설정 섹션을 봐주세요.
단일 설정을 객체, 함수 또는 promise로 export 하는 것과 함께, 다중 설정을 export 할 수 있습니다.
다중 설정 내보내기를 봐주세요.
Webpack은 다양한 프로그래밍 및 데이터 언어로 작성된 설정 파일을 허용합니다.
설정 언어를 봐주세요.
모듈형 프로그래밍(modular programmming)에서 개발자는 모듈이라는 개별 기능으로 프로그램을 나눕니다.
각 모듈은 전체 프로그램보다 영향 범위가 좁기 때문에 검증과 디버깅 및 테스트가 간단합니다. 잘 작성된 모듈은 견고한 추상화와 캡슐화의 경계를 만들므로 각 모듈은 전체 애플리케이션에서 일관성 있는 설계와 명확한 목적을 가질 수 있습니다.
Node.js는 거의 시작부터 모듈형 프로그래밍을 지원했습니다. 하지만 웹에서는 모듈의 지원이 느리게 정착해왔습니다. 웹에서 모듈형 JavaScript를 지원하는 여러 도구가 존재하며 다양한 이점과 제한이 있습니다. Webpack은 이러한 시스템에서 얻은 교훈을 바탕으로 제작되어 프로젝트의 모든 파일에 모듈의 개념을 사용합니다.
Node.js 모듈과 달리 webpack 모듈은 다양한 방식으로 의존성을 표현할 수 있습니다. 몇 가지 예는 다음과 같습니다.
import
문require()
문define
과 require
문@import
문.url(...)
의 이미지 URL 또는 HTML <img src=...>
파일Webpack은 기본적으로 다음 유형의 모듈을 지원합니다.
그 밖에도 webpack은 여러 언어로 작성된 모듈과 로더를 통한 다양한 전처리기를 지원합니다. 로더는 webpack에서 네이티브가 아닌 모듈을 어떻게 처리하고 이러한 의존성을 번들에 포함할지 정의합니다. webpack 커뮤니티에서는 다음과 같이 널리 사용되는 다양한 언어와 언어 프로세서를 위한 로더를 제작했습니다.
그 외 다른 많은 것들을 지원합니다! 종합하면, webpack은 커스터마이징을 위한 강력하고 풍부한 API를 제공하여 어떤 환경에서도 webpack을 사용할 수 있도록 하는 동시에, 개발과 테스트 및 프로덕션 작업 흐름을 유연하게 유지하도록 합니다.
전체 목록은 로더 목록 또는 로더 직접 작성하기(write your own)를 참고하세요.
리졸버는 절대 경로로 모듈을 찾는 데 도움이 되는 라이브러리입니다. 모듈은 다음과 같이 다른 모듈의 의존성으로서 필요할 수 있습니다.
import foo from 'path/to/module';
// 또는
require('path/to/module');
의존성 모듈은 애플리케이션 코드 또는 써드 파티 라이브러리에서 가져올 수 있습니다.
리졸버는 webpack이 모든 require
/import
문에 대해 번들에 포함되어야 하는 모듈의 코드를 찾는 데 도움을 줍니다.
webpack은 모듈을 번들링하는 동안 파일 경로를 확인하기 위해 enhanced-resolve를 사용합니다.
enhanced-resolve
를 사용하여 webpack은 세 가지 종류의 파일 경로를 확인할 수 있습니다.
import '/home/me/file';
import 'C:\\Users\\me\\file';
이미 파일에 대한 절대 경로가 있으므로 추가 분석이 필요하지 않습니다.
import '../src/file1';
import './file2';
이 경우 import
또는 require
가 발생하는 소스 파일의 디렉터리를 컨텍스트 디렉터리로 간주합니다. import/require
에 지정된 상대 경로는 이 컨텍스트 경로에 결합되어 모듈에 대한 절대 경로를 생성합니다.
import 'module';
import 'module/lib/file';
모듈은 resolve.modules
에 지정된 모든 디렉터리 내에서 검색됩니다.
resolve.alias
구성 옵션을 사용하여 별칭을 만들어 원래 모듈 경로를 대체 경로로 바꿀 수 있습니다.
package.json
파일이 포함된 경우 resolve.exportsFields
구성 옵션에 지정된 필드를 순서대로 조회하고 package.json
의 첫 번째 필드는 패키지 내보내기 가이드라인에 따라 패키지에서 사용 가능한 내보내기를 결정합니다.위의 규칙에 따라 경로가 확인되면 리졸버는 경로가 파일 또는 디렉터리를 가리키는지 확인합니다. 경로가 파일을 가리키는 경우 다음 단계를 수행합니다.
resolve.extensions
옵션을 사용하여 확인됩니다. 예시: .js
, .jsx
.경로가 폴더를 가리키는 경우 올바른 확장자를 가진 올바른 파일을 찾기 위해 다음 단계를 수행합니다.
package.json
파일이 포함되어 있으면 resolve.mainFields
구성 옵션에 지정된 필드가 순서대로 조회되고 package.json
의 첫 번째 필드가 파일 경로를 결정합니다.package.json
이 없거나 resolve.mainFields
가 유효한 경로를 반환하지 않는 경우 resolve.mainFields
구성 옵션에 지정된 파일 이름을 순서대로 검색하여 imported/required 된 디렉터리에 일치하는 파일 이름이 있는지 확인합니다.resolve.extensions
옵션을 사용하여 비슷한 방식으로 파일 확장자를 확인합니다.Webpack은 빌드 대상에 따라 이러한 옵션에 대해 합리적인 기본값을 제공합니다.
파일 분석에 명시된 것과 동일한 규칙을 따릅니다. 그러나 resolveLoader
구성 옵션을 사용하여 로더에 대한 별도의 분석 규칙을 가질 수 있습니다.
모든 파일 시스템 액세스가 캐시되므로 동일한 파일에 대한 여러 병렬 또는 직렬 요청이 더 빠르게 발생합니다. watch 모드에서는 수정된 파일만 캐시에서 제거됩니다. watch 모드가 꺼져 있으면 모든 컴파일 전에 캐시가 제거됩니다.
위에서 언급한 구성 옵션에 대한 자세한 내용은 Resolve API를 참고하세요.
여러 개의 개별 빌드가 단일 애플리케이션을 형성해야 합니다. 이러한 개별 빌드는 컨테이너처럼 작동하며, 빌드 간에 서로 코드를 노출하고 소비하여 단일 통합 애플리케이션을 생성할 수 있습니다.
이것은 종종 Micro-Frontends라고 알려져 있지만, 그것에만 국한되지는 않습니다.
로컬 모듈과 원격 모듈을 구별합니다. 로컬 모듈은 현재 빌드의 일부인 일반 모듈입니다. 원격 모듈은 현재 빌드의 일부가 아니며 원격 컨테이너에서 런타임에 로드되는 모듈입니다.
원격 모듈을 로드 하는 것은 비동기 작업으로 간주됩니다. 원격 모듈을 사용할 때 이러한 비동기 작업은 원격 모듈과 엔트리 포인트 사이에 있는 다음 청크 로드 작업에 배치됩니다. 청크 로드 작업 없이는 원격 모듈을 사용할 수 없습니다.
청크 로드 작업은 일반적으로 import()
를 호출하는 것이지만, require.ensure
또는 require([...])
같은 이전 구조도 지원됩니다.
특정 모듈에 대한 비동기 접근을 노출하는 컨테이너 엔트리를 통해 컨테이너가 생성됩니다. 노출된 접근은 두 단계로 구분됩니다.
1단계는 청크 로드 중에 수행됩니다. 2단계는 다른 로컬 및 원격 모듈과 인터리브된 모듈 평가 중에 수행됩니다. 이렇게 하면, 모듈을 로컬에서 원격으로 또는 그 반대로 변환해도 평가 순서가 영향을 받지 않습니다.
컨테이너를 중첩할 수 있습니다. 컨테이너는 다른 컨테이너의 모듈을 사용할 수 있습니다. 컨테이너 간의 순환 의존성도 가능합니다.
각각의 빌드는 컨테이너의 역할을 하며 다른 빌드를 컨테이너로 소비하기도 합니다. 이렇게 하면 각각의 빌드가 해당 컨테이너에서 로드 하여 다른 노출된 모듈에 접근할 수 있습니다.
공유된 모듈은 오버라이딩 가능하고 중첩된 컨테이너에 오버라이드로 제공됩니다. 일반적으로 각 빌드에서 동일한 모듈(예, 동일한 라이브러리)을 가리킵니다.
packageName
옵션을 사용하면 requiredVersion
을 찾도록 패키지 이름을 설정할 수 있습니다. 기본적으로 모듈 요청에 자동으로 추론되며, 자동 추론을 비활성화해야 하는 경우 requiredVersion
을 false
로 설정합니다.
이 플러그인은 지정된 노출 모듈로 추가 컨테이너 엔트리를 생성합니다.
이 플러그인은 컨테이너에 대한 특정 참조를 외부로 추가하고 컨테이너에서 원격 모듈을 가져올 수 있도록 합니다. 또한 컨테이너의 override
API를 호출하여 오버라이드를 제공합니다. 로컬 오버라이드 (빌드도 컨테이너인 경우 __webpack_override__
또는 override
API을 통해) 그리고 지정된 오버라이드가 모든 참조된 컨테이너에게 제공됩니다.
ModuleFederationPlugin
은 ContainerPlugin
과 ContainerReferencePlugin
을 결합합니다.
config.context
에서 확인할 수 있습니다.requiredVersion
을 사용하지 않습니다.requiredVersion
을 추출합니다./
가 있는 모듈 요청은 이 접두사의 모든 모듈 요청과 일치합니다.단일 페이지 애플리케이션의 각 페이지는 별도의 빌드에서 컨테이너 빌드로부터 노출됩니다. 또한 애플리케이션 쉘은 모든 페이지를 원격 모듈로 참조하는 별도의 빌드입니다. 이렇게 하면 각 페이지를 별도로 배포할 수 있습니다. 경로가 업데이트되거나 새 경로가 추가되면 애플리케이션 쉘이 배포됩니다. 애플리케이션 쉘은 페이지 빌드에서 중복을 방지하기 위해 일반적으로 사용되는 라이브러리를 공유 모듈로 정의합니다.
많은 애플리케이션은 각 컴포넌트가 노출된 컨테이너로 빌드 할 수 있는 공통 컴포넌트 라이브러리를 공유합니다. 각 애플리케이션은 컴포넌트 라이브러리 컨테이너의 컴포넌트를 사용합니다. 모든 애플리케이션을 다시 배포할 필요 없이 컴포넌트 라이브러리의 변경을 별도로 배포할 수 있습니다. 애플리케이션은 컴포넌트 라이브러리의 최신 버전을 자동으로 사용합니다.
컨테이너 인터페이스는 get
및 init
메소드를 지원합니다.
init
은 하나의 인수(공유 범위 객체)로 호출되는 async
호환 메소드입니다. 이 객체는 원격 컨테이너에서 공유 범위로 사용되며 호스트에서 제공된 모듈로 채워집니다.
원격 컨테이너를 런타임에 동적으로 호스트 컨테이너에 연결하는데 사용할 수 있습니다.
init.js
(async () => {
// 공유 범위를 초기화 합니다. 이 빌드와 모든 원격에서 제공된 모듈로 채웁니다
await __webpack_init_sharing__('default');
const container = window.someContainer; // 또는 다른 곳에서 컨테이너를 얻으십시오
// 컨테이너 초기화, 공유 모듈 제공 가능합니다
await container.init(__webpack_share_scopes__.default);
const module = await container.get('./module');
})();
컨테이너는 공유 모듈을 제공하려고 시도하지만, 공유 모듈이 이미 사용된 경우, 경고 및 제공된 공유 모듈이 무시됩니다. 컨테이너는 여전히 대체 수단으로 사용할 수 있습니다.
이렇게 하면 다른 버전의 공유 모듈을 제공하는 A/B 테스트를 동적으로 로드 할 수 있습니다.
예:
init.js
function loadComponent(scope, module) {
return async () => {
// 공유 범위를 초기화 합니다. 이 빌드와 모든 원격에서 제공된 모듈로 채웁니다
await __webpack_init_sharing__('default');
const container = window[scope]; // 또는 다른 곳에서 컨테이너를 얻으십시오
// 컨테이너 초기화, 공유 모듈 제공 가능합니다
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
};
}
loadComponent('abtests', 'test123');
일반적으로, 원격은 다음 예시와 같은 URL을 사용하여 설정합니다.
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
},
}),
],
};
하지만 원격에 promise를 전달할 수도 있고, 이는 런타임에 해석됩니다. 위에서 설명한 get/init
인터페이스에 맞는 모듈로 이 promise를 해석해야 합니다. 예를 들어 사용할 통합 모듈의 버전을 전달하려는 경우 쿼리 매개 변수를 통해 다음과 같은 작업을 수행할 수 있습니다.
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: `promise new Promise(resolve => {
const urlParams = new URLSearchParams(window.location.search)
const version = urlParams.get('app1VersionParam')
// 이 부분은 통합 모듈 호스팅 및 버전의 관리 계획에 따라 달라집니다.
const remoteUrlWithVersion = 'http://localhost:3001/' + version + '/remoteEntry.js'
const script = document.createElement('script')
script.src = remoteUrlWithVersion
script.onload = () => {
// 주입된 스크립트가 로드되어 window에서 사용할 수 있습니다.
// 이제 Promise를 해석할 수 있습니다.
const proxy = {
get: (request) => window.app1.get(request),
init: (...arg) => {
try {
return window.app1.init(...arg)
} catch(e) {
console.log('remote container already initialized')
}
}
}
resolve(proxy)
}
// 이 스크립트를 버전이 지정된 remoteEntry.js로 src 세트와 함께 주입합니다.
document.head.appendChild(script);
})
`,
},
// ...
}),
],
};
이 API를 사용할 때는 get/init API가 포함된 개체를 확인해야 합니다.
호스트가 원격 모듈의 메소드를 노출하여 런타임 원격 모듈의 publicPath를 설정하도록 허용할 수 있습니다.
이 접근 방식은 호스트 도메인의 하위 경로에 독립적으로 배포된 하위 응용 프로그램을 마운트 할 때 특히 유용합니다.
시나리오입니다.
https://my-host.com/app/*
에 호스팅 된 호스트 앱과 https://foo-app.com
에 호스팅 된 하위 앱이 있습니다.
하위 앱도 호스트에 마운트되므로, https://foo-app.com
은 https://my-host.com/app/foo-app
와 https://my-host.com/app/foo-app/*
를 통해 접근할 수 있습니다. 요청은 프록시를 통해 https://foo-app.com/*
로 리다이렉션 됩니다.
예제입니다.
webpack.config.js (remote)
module.exports = {
entry: {
remote: './public-path',
},
plugins: [
new ModuleFederationPlugin({
name: 'remote', // 이 이름은 엔트리 이름과 일치해야 합니다.
exposes: ['./public-path'],
// ...
}),
],
};
public-path.js (remote)
export function set(value) {
__webpack_public_path__ = value;
}
src/index.js (host)
const publicPath = await import('remote/public-path');
publicPath.set('/your-public-path');
//bootstrap app e.g. import('./bootstrap.js')
document.currentScript.src
의 스크립트 태그에서 publicPath를 유추하고 런타임에 __webpack_public_path__
모듈 변수로 설정할 수 있습니다.
예제입니다.
webpack.config.js (remote)
module.exports = {
entry: {
remote: './setup-public-path',
},
plugins: [
new ModuleFederationPlugin({
name: 'remote', // 이 이름은 엔트리 이름과 일치해야 합니다.
// ...
}),
],
};
setup-public-path.js (remote)
// 자체 로직으로 publicPath를 파생하고 __webpack_public_path__ API로 설정하세요.
__webpack_public_path__ = document.currentScript.src + '/../';
Uncaught Error: Shared module is not available for eager consumption
애플리케이션은 전 방향 호스트로 작동하는 애플리케이션을 실행하고 있습니다. 선택할 수 있는 옵션이 있습니다.
모듈을 비동기 청크에 넣지 않고 동기식으로 제공하는 Module Federation 고급 API 내에서 의존성을 eager로 설정할 수 있습니다. 이를 통해 초기 청크에서 이러한 공유 모듈을 사용할 수 있습니다. 그러나 제공된 모든 모듈과 예비 모듈은 항상 다운로드 되므로 주의하세요. 쉘 같이 애플리케이션의 한 지점에서만 제공하는 것이 좋습니다.
비동기 경계를 사용하는 것을 추천합니다. 추가 왕복을 방지하고 일반적인 성능 향상을 위해 더 큰 청크의 초기화 코드를 분할합니다.
예를 들어, 엔트리는 다음과 같습니다.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
bootstrap.js
파일을 만들고 엔트리의 콘텐츠을 이 파일로 이동한 다음, 부트스트랩을 엔트리로 가져오겠습니다.
index.js
+ import('./bootstrap');
- import React from 'react';
- import ReactDOM from 'react-dom';
- import App from './App';
- ReactDOM.render(<App />, document.getElementById('root'));
bootstrap.js
+ import React from 'react';
+ import ReactDOM from 'react-dom';
+ import App from './App';
+ ReactDOM.render(<App />, document.getElementById('root'));
이 메소드는 동작하지만 제약사항이나 단점이 있을 수 있습니다.
ModuleFederationPlugin
을 통해 의존성을 eager: true
로 설정합니다.
webpack.config.js
// ...
new ModuleFederationPlugin({
shared: {
...deps,
react: {
eager: true,
},
},
});
Uncaught Error: Module "./Button" does not exist in container.
"./Button"
은 표시되지 않지만, 오류 메세지는 유사하게 표시됩니다. 이 문제는 일반적으로 webpack beta.16에서 webpack beta.17로 업그레이드 하는 경우에 나타납니다.
ModuleFederationPlugin 내에서 다음 위치에서 exposes를 변경합니다.
new ModuleFederationPlugin({
exposes: {
- 'Button': './src/Button'
+ './Button':'./src/Button'
}
});
Uncaught TypeError: fn is not a function
원격 컨테이너가 누락되었을 가능성이 있으므로 추가되었는지 확인하세요. 사용하려는 원격 컨테이너가 로드되었지만 이 오류가 계속 나타나면 호스트 컨테이너의 원격 컨테이너 파일도 HTML에 추가하세요.
서로 다른 원격에서 여러 모듈을 로드하는 경우 원격 빌드에 대해 output.uniqueName
을 설정하여 여러 webpack 런타임 간의 충돌을 방지하는 것이 좋습니다.
하나의 파일이 다른 파일에 의존할 때마다, webpack은 이것을 의존성으로 취급합니다. 이를 통해 webpack은 이미지 또는 웹 폰트와 같은 코드가 아닌 애셋을 가져와, 애플리케이션에 의존성으로 제공할 수 있습니다.
webpack은 애플리케이션을 처리할 때, 커맨드 라인 또는 설정 파일에 정의 된 모듈 목록에서 시작합니다. 엔트리 포인트에서 시작하여, webpack은 애플리케이션에서 필요한 모든 모듈을 포함하는 디펜던시 그래프를 재귀적으로 빌드한 다음, 모든 모듈을 브라우저에 의해 로드 되는 작은 수(보통 하나)의 번들로 묶습니다.
JavaScript는 서버와 클라이언트 모두 작성이 가능하기 때문에 webpack은 webpack 설정에서 다수의 배포 대상을 제공합니다.
target
프로퍼티를 설정하려면 webpack 설정에서 target 값을 설정하기만 하면 됩니다.
webpack.config.js
module.exports = {
target: 'node',
};
위 예제에서 node
webpack을 사용하면 Node.js와 유사한 환경에서 사용할 수 있도록 컴파일됩니다. (Node.js의 require
를 사용하여 청크를 로드하고 fs
나 path
와 같은 모듈은 수정하지 않습니다.)
각 target은 배포와 환경에 관련된 다양한 추가 기능이 있으며, 필요에 맞게 지원됩니다. 사용 가능한 target을 확인하세요.
webpack에서는 한 개 이상의 문자열을 target
프로퍼티에 전달할 수 없습니다. 하지만 두 개의 개별 설정을 번들링하여 동일한 라이브러리를 생성할 수 있습니다.
webpack.config.js
const path = require('path');
const serverConfig = {
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.node.js',
},
//…
};
const clientConfig = {
target: 'web', // <=== 기본값은 'web'이므로 생략 가능
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.js',
},
//…
};
module.exports = [serverConfig, clientConfig];
위 예제에서는 dist
폴더에 lib.js
와 lib.node.js
파일을 생성합니다.
위의 옵션에서 볼 수 있듯이 여러 배포 target을 선택할 수 있습니다. 다음은 참고할 수 있는 예제 및 리소스 목록입니다.
webpack으로 빌드된 일반적인 애플리케이션 또는 사이트에는 세 가지 주요 유형의 코드가 있습니다.
이 글은 이 세 부분 중 마지막인 런타임과 특히 매니페스트에 초점을 맞추어 설명합니다.
매니페스트 데이터와 함께 런타임은 기본적으로 webpack이 브라우저에서 실행되는 동안 모듈화된 애플리케이션을 연결하는 데 필요한 모든 코드입니다. 모듈이 상호 작용할 때 모듈을 연결하는 데 필요한 로딩 및 해석 로직이 포함되어 있습니다. 여기에는 브라우저에 이미 로드된 모듈 연결과 그렇지 않은 모듈을 지연 로드하는 로직이 포함됩니다.
애플리케이션이 index.html
파일 형식으로 브라우저에 도달하면 애플리케이션에 필요한 일부 번들 및 기타 다양한 애셋을 로드하고 연결해야 합니다. 꼼꼼하게 배치한 /src
디렉터리는 이제 번들이 되고 압축되며 지연 로딩을 위해 webpack의 최적화
에 의하여 더 작은 청크로 분할될 수 있습니다. 그렇다면 webpack은 필요한 모든 모듈 간의 상호 작용을 어떻게 관리할까요? 매니페스트 데이터로 관리가 됩니다.
컴파일러가 애플리케이션에 입력, 해석 및 매핑 할 때 모든 모듈에 대한 자세한 메모를 유지합니다. 이 데이터 모음을 "매니페스트"라고 하며, 모듈이 번들링 되고 브라우저에 제공되면 런타임에서 모듈을 해석하고 로드하는데 사용합니다. 선택한 모듈 구문에 관계없이 import
또는 require
문은 이제 모듈 식별자를 가리키는 __webpack_require__
메소드가 됩니다. 매니페스트의 데이터를 사용하여 런타임은 식별자 뒤에 있는 모듈을 검색할 위치를 찾을 수 있습니다.
이제 webpack이 내부적으로 어떻게 작동하는지에 대한 약간의 통찰력을 얻었습니다. "이것이 나에게 어떤 영향을 미칩니까?"라고 질문 할 수 있습니다. 이에 대한 간단한 대답은 대부분 그렇지 않다입니다. 런타임은 매니페스트를 활용하여 작업을 수행하며 애플리케이션이 브라우저에 도달하면 모든 것이 마술처럼 작동하는 것처럼 보입니다. 그러나 브라우저 캐싱을 활용하여 프로젝트의 성능을 향상하기로 결정했다면 이 프로세스는 이해해야 할 중요한 사항이 될 것입니다.
번들 파일 이름 내에서 콘텐츠 해시를 사용하면 파일 콘텐츠가 변경된 시기를 브라우저에 표시하여 캐시를 무효화 할 수 있습니다. 이 작업을 시작하면 즉시 몇 가지 재미있는 행동을 발견 할 수 있습니다. 특정 해시는 내용이 분명히 변경되지 않더라도 변경됩니다. 이는 모든 빌드를 변경하는 런타임 및 매니페스트의 주입으로 인해 발생합니다.
Output management 가이드의 매니페스트 섹션을 참조하여 매니페스트를 추출하는 방법을 알아보고 장기 캐싱의 복잡성에 대해 자세히 알아보려면 아래 가이드를 읽어보세요.
Hot Module Replacement(HMR)는 모듈 전체를 다시 로드하지 않고 애플리케이션이 실행되는 동안 교환, 추가 또는 제거합니다. 다음과 같은 몇 가지 방법으로 개발 속도를 크게 높일 수 있습니다.
HMR이 어떻게 작동하는지 정확히 이해하기 위해 몇 가지 다른 관점을 살펴보겠습니다.
다음 단계를 통해 애플리케이션에서 모듈을 교체할 수 있습니다.
이 프로세스가 자동으로 발생하도록 HMR을 설정하거나 업데이트가 발생하기 위해 사용자 상호 작용을 요구하도록 선택할 수 있습니다.
일반 애셋 외에도 컴파일러는 이전 버전에서 새 버전으로 업데이트할 수 있도록 "업데이트"를 내보내야 합니다. "업데이트"는 두 부분으로 구성됩니다.
매니페스트에는 새 컴파일 해시와 업데이트된 모든 청크 목록이 포함됩니다. 각 청크에는 업데이트된 모든 모듈에 대한 새 코드(또는 모듈이 제거되었음을 나타내는 플래그)가 포함됩니다.
컴파일러는 빌드 간에 모듈 ID와 청크 ID가 일치하는지 확인합니다. 일반적으로 이러한 ID를 메모리에 저장하지만 (예: webpack-dev-server 사용) JSON 파일에 저장할 수도 있습니다.
HMR은 HMR 코드가 포함된 모듈에만 영향을 미치는 선택적인 기능입니다. 한 가지 예는 style-loader
를 통해 스타일을 가져오는 것입니다. 패치가 작동하기 위해 style-loader
는 HMR 인터페이스를 구현합니다. HMR을 통해 업데이트를 받으면 이전 스타일을 새 스타일로 대체합니다.
마찬가지로 모듈에서 HMR 인터페이스를 구현할 때 모듈이 업데이트될 때 어떤 일이 발생해야 하는지 설명할 수 있습니다. 그러나 대부분의 경우 모든 모듈에서 HMR 코드를 작성해야 하는 것은 아닙니다. 모듈에 HMR 핸들러가 없으면 업데이트가 버블링됩니다. 이는 단일 핸들러가 전체 모듈 트리를 업데이트 할 수 있음을 의미합니다. 트리의 단일 모듈이 업데이트되면 전체 종속성 집합이 다시 로드됩니다.
module.hot
인터페이스에 대한 자세한 내용은 HMR API 페이지를 참고하세요.
여기에서는 조금 더 기술적인 내용을 다룹니다. 내부 동작에 관심이 없다면 HMR API 페이지 또는 HMR 가이드로 이동하세요.
모듈 시스템 런타임의 경우 부모
및 자식
모듈을 추적하기 위해 추가 코드가 생성됩니다. 관리적인 측면에서 런타임은 check
그리고 apply
두 가지 방법을 지원합니다.
check
는 업데이트 매니페스트에 HTTP 요청을 합니다. 요청이 실패하면 가능한 업데이트가 없음을 의미합니다. 성공하면 업데이트된 청크 목록과 현재 로드된 청크 목록을 비교합니다. 로드된 각 청크에 해당하는 업데이트 청크가 다운로드됩니다. 모든 모듈 업데이트는 런타임에 저장됩니다. 모든 업데이트 청크가 다운로드되고 적용할 준비가 되면 런타임이 ready
상태로 전환됩니다.
apply
는 업데이트된 모든 모듈을 유효하지 않은 것으로 표시합니다. 유효하지 않은 각 모듈에 대해 해당 모듈 또는 상위 모듈에 업데이트 핸들러가 있어야 합니다. 그렇지 않으면 잘못된 플래그가 버블링되고 부모도 무효가 됩니다. 앱의 엔트리 포인트나 업데이트 핸들러가 있는 모듈에 도달할 때까지(둘 중 먼저 오는 쪽) 계속 버블링됩니다. 엔트리 포인트에서 버블링이 발생하면 프로세스가 실패합니다.
그 후 모든 유효하지 않은 모듈이 (폐기 처리기를 통해) 폐기되고 언로드됩니다. 그런 다음 현재 해시가 업데이트되고 모든 accept
핸들러가 호출됩니다. 런타임은 idle
상태로 다시 전환되고 모든 것이 정상적으로 계속됩니다.
HMR은 Live Reload의 대체품으로 개발에 사용할 수 있습니다. webpack-dev-server는 전체 페이지를 다시 로드하기 전에 HMR로 업데이트를 시도하는 hot
모드를 지원합니다. 자세한 내용은 Hot Module Replacement 가이드를 참고하세요.
Webpack을 사용해야 하는 이유를 이해하기 위해서 번들러가 사용되기 전에 어떻게 JavaScript를 사용했었는지 요약해 보겠습니다.
브라우저에서 JavaScript가 동작하는 두 가지 방법이 있습니다. 먼저 각 기능이 있는 스크립트를 추가합니다. 이 방식은 너무 많은 스크립트로 인해 네트워크의 병목을 유발하는 원인이 될 수 있어서 확장이 어렵습니다. 두 번째 옵션은 모든 프로젝트에 하나의 거대한 .js
파일을 만들어 사용하는 것입니다. 그러나 이것은 유효범위와 크기, 가독성, 유지보수에 문제를 발생시킬 수 있습니다.
IIFE는 대규모 프로젝트에서 유효범위 문제를 해결합니다. 스크립트 파일을 IIFE로 감싸면 유효범위에 대한 걱정 없이 파일을 안전하게 연결하거나 결합 할 수 있습니다.
IIFE의 사용은 Make, Gulp, Grunt, Broccoli, Brunch와 같은 툴과 연관이 있습니다. 이 툴들은 태스크 러너라고 하며, 모든 프로젝트 파일들을 함께 연결합니다.
그러나 하나의 파일을 변경해도 전체를 다시 빌드해야 합니다. 연결하면 파일 간의 스크립트를 쉽게 재사용 할 수 있지만, 빌드 최적화를 더욱더 어렵게 합니다. 코드가 실제로 사용되고 있는지 어떻게 알 수 있을까요?
만약 lodash에서 하나의 함수만 사용해도, 전체 라이브러리를 추가하고 모든 것을 뭉쳐야 합니다. 코드의 의존성을 어떻게 treeshaking 할까요? 지연 로딩 청크 코드는 확장이 어렵고 개발자의 많은 수동 작업을 필요로 합니다.
Webpack은 브라우저 외부 환경의 서버나 컴퓨터에서 사용할 수 있는 JavaScript 런타임인 Node.js에서 동작합니다.
Node.js가 출시되었을 때 새로운 시대가 시작되었으며, 새로운 도전이었습니다. 현재 JavaScript는 브라우저에서 동작하지 않는데 어떻게 Node 애플리케이션은 새로운 코드 청크를 불러오는 것일까요? 여기에는 추가 할 수 있는 스크립트 태그나 html 파일이 없습니다.
require
를 도입한 CommonJS가 출시되었는데 이는 현재 파일에서 모듈을 불러오고 사용할 수 있습니다. 이것은 필요한 곳에 모듈을 가져와 별도의 구성 없이 바로 사용할 수 있도록 하여 유효범위 문제를 해결했습니다.
JavaScript는 언어로서, 플랫폼으로서, 빠른 개발 방식, 빠른 애플리케이션을 만드는 방법으로 세계적으로 사용되고 있습니다.
그러나 CommonJS에 대한 브라우저의 지원은 없습니다. 라이브 바인딩도 없습니다. 순환 참조의 문제가 있으며, 동기적인 모듈 해석과 로딩이 느립니다. CommonJS가 Node.js 프로젝트에서는 뛰어난 솔루션이었지만 브라우저는 모듈을 지원하지 않았습니다. 때문에 브라우저에서 CommonJS의 실행을 가능하게 하는 Browserify와 RequireJS, SystemJS 같은 번들러와 툴들이 만들어졌습니다.
웹 프로젝트에서 좋은 소식은 모듈이 ECMAScript 표준에서 공식 기능이 되고 있다는 것입니다. 그러나 브라우저 지원은 불완전하고, 번들링이 여전히 더 빠르기 때문에 현재 초기 구현 모듈보다 권장되고 있습니다.
구식의 태스크 러너와 Google Closure Compiler 조차 모든 의존성을 미리 수동으로 선언해야 합니다. Webpack같은 번들러는 자동으로 빌드하고, 가져오거나 내보낸 항목을 기반으로 디펜던시 그래프를 추론합니다. 이는 다른 플러그인과 로더와 함께 훌륭한 개발자 경험을 제공합니다.
모듈을 작성할 수도 있고, 어떠한 포맷의 모듈(적어도 ESM에 도달하기 전까지)도 지원하며, 리소스와 애셋을 동시에 처리 가능한 것이 있으면 좋지 않을까요?
이것이 webpack이 존재하는 이유입니다. JavaScript 애플리케이션을 번들로 묶을 수 있는(ESM과 CommonJS 모두 지원) 도구이며, 이미지나 폰트, 스타일 시트 같은 다양한 애셋을 지원하도록 확장할 수 있습니다.
Webpack은 성능과 로딩 시간을 중요하게 생각합니다. 프로젝트나 사용자에게 최고의 경험을 제공하기 위해 항상 비동기 청크 로딩이나 프리패칭 같은 새로운 기능을 추가하거나 개선하고 있습니다.
이 섹션은 webpack의 내부 요소를 설명하며 플러그인 개발자에게 유용할 수 있습니다.
번들링은 일부 파일을 가져오고 다른 파일은 내보내는 기능입니다.
그러나 입력과 출력 사이에는 모듈, 엔트리 포인트, 청크, 청크 그룹, 그 외 많은 중간 요소들이 있습니다.
프로젝트에서 사용하는 모든 파일은 모듈입니다.
./index.js
import app from './app.js';
./app.js
export default 'the app';
모듈은 서로를 사용하여 그래프(ModuleGraph
)를 형성합니다.
번들링 과정 중에 모듈은 청크로 결합됩니다.
청크는 청크 그룹으로 합쳐지고, 모듈을 통해 서로 연결된 그래프(ChunkGraph
)를 형성합니다.
내부적으로 엔트리 포인트를 설명할 때는 하나의 청크로 청크 그룹을 만드는 것을 말합니다.
./webpack.config.js
module.exports = {
entry: './index.js',
};
main
이라는 이름으로 하나의 청크 그룹이 생성됩니다. (main
은 엔트리 포인트의 기본 이름입니다.)
이 청크 그룹에는./index.js
모듈이 포함되어 있습니다. 파서가 ./index.js
내부의 import 문을 처리하면서 새 모듈이 이 청크에 추가됩니다.
다른 예제를 확인해보세요.
./webpack.config.js
module.exports = {
entry: {
home: './home.js',
about: './about.js',
},
};
이름이 home
과 about
인 두 개의 청크 그룹이 생성됩니다.
각각의 청크 그룹은 모듈이 있는 청크를 가지고 있습니다. home
은 ./home.js
를, about
은 ./about.js
청크를 가지고 있습니다.
청크 그룹에는 하나 이상의 청크가 있을 수 있습니다. 예를 들어 SplitChunksPlugin은 청크를 하나 또는 그 이상의 청크로 분할합니다.
청크는 두 가지 형태로 제공됩니다.
초기 청크
는 엔트리 포인트의 메인 청크입니다. 이 청크는 엔트리 포인트에서 명시된 모든 모듈과 의존성을 포함합니다.비초기 청크
는 지연 로드될 수 있는 청크입니다. 동적 import 또는 SplitChunksPlugin 사용 중에 나타날 수 있습니다.각 청크에는 해당하는 애셋이 있습니다. 애셋은 번들링의 결과로 출력된 파일입니다.
webpack.config.js
module.exports = {
entry: './src/index.jsx',
};
./src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import('./app.jsx').then((App) => {
ReactDOM.render(<App />, root);
});
이름이 main
인 초기 청크가 생성되며, 이 청크는 다음을 포함합니다.
./src/index.jsx
react
react-dom
그리고 ./app.jsx
를 제외한 모든 의존성도 포함합니다.
./app.jsx
모듈은 동적으로 가져오므로 비초기 청크가 생성됩니다.
결과:
/dist/main.js
- 초기
청크/dist/394.js
- 비초기
청크기본적으로 비초기
청크에는 이름이 없으므로 이름 대신 고유한 ID가 사용됩니다.
동적으로 가져올 때 "특별한" 주석을 사용하여 청크 이름을 구체적으로 지정할 수 있습니다.
import(
/* webpackChunkName: "app" */
'./app.jsx'
).then((App) => {
ReactDOM.render(<App />, root);
});
결과:
/dist/main.js
- 초기
청크/dist/app.js
- 비초기
청크출력 파일의 이름은 설정의 두 필드에 영향을 받습니다.
output.filename
- 초기
청크 파일에서 사용합니다.output.chunkFilename
- 비초기
청크 파일에서 사용합니다.초기
와 비초기
로 사용합니다. 이 때는 output.filename
을 사용합니다.이 필드에는 몇 개의 플레이스 홀더를 제공합니다. 가장 자주 사용하는 것은 아래와 같습니다.
[id]
- 청크 ID (예: [id].js
-> 485.js
)[name]
- 청크 이름 (예: [name].js
-> app.js
). 청크에 이름이 없는 경우 ID가 사용됩니다.[contenthash]
- 출력 파일 콘텐츠의 md4-hash (예: [contenthash].js
-> 4ea6ff1de66c537eb9b2.js
)