플러그인은 서드파티 개발자들에게 webpack 엔진의 모든 잠재력을 보여줍니다. 단계별로 빌드 callback을 사용하여 개발자들은 webpack의 빌드 프로세스에 자신만의 행동을 도입할 수 있습니다. 플러그인을 빌드하는 것은 로더를 빌딩 하는 것보다 조금 더 진보되었습니다. 왜냐하면, 플러그인은 몇몇 webpack의 low-level에 내장된 훅을 이해할 필요가 있기 때문입니다. 이제 소스 코드를 읽어볼 준비를 해봅시다!
Webpack용 plugin은 다음과 같이 구성됩니다.
apply
메소드를 정의합니다.// A JavaScript class.
class MyExampleWebpackPlugin {
// `apply`를 compiler를 인자로 받는 프로토타입 메소드로 정의합니다.
apply(compiler) {
// attach할 이벤트 훅을 지정합니다.
compiler.hooks.emit.tapAsync(
'MyExampleWebpackPlugin',
(compilation, callback) => {
console.log('This is an example plugin!');
console.log(
'Here’s the `compilation` object which represents a single build of assets:',
compilation
);
// Webpack에서 제공되는 플러그인 API를 사용하여 빌드를 처리합니다.
compilation.addModule(/* ... */);
callback();
}
);
}
}
플러그인은 apply
라는 프로토타입 메소드로 인스턴스화 된 객체입니다. 이러한 apply
메소드는 플러그인을 설치하는 동안 webpack 컴파일러에 의해 한번 호출됩니다. apply
메소드는 컴파일러 callback에 대해 접근 권한을 부여하는 기본 webpack 컴파일러에 의해 참조됩니다. 플러그인의 구조는 다음과 같습니다.
class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap(
'Hello World Plugin',
(stats /* 훅의 탭이 끝나면 stats는 인자로 통과됩니다.*/) => {
console.log('Hello World!');
}
);
}
}
module.exports = HelloWorldPlugin;
그런 다음에 플러그인을 사용하려면 webpack의 설정인 plugins
배열에 인스턴스를 포함해야합니다.
// webpack.config.js
var HelloWorldPlugin = require('hello-world');
module.exports = {
// ... 여기서 구성을 설정할 것 ...
plugins: [new HelloWorldPlugin({ options: true })],
};
플러그인 옵션을 통해 전달되는 옵션의 유효성을 확인하려면 schema-utils
를 사용하세요. 다음은 사용 예제입니다.
import { validate } from 'schema-utils';
// options 객체에 대한 schema
const schema = {
type: 'object',
properties: {
test: {
type: 'string',
},
},
};
export default class HelloWorldPlugin {
constructor(options = {}) {
validate(schema, options, {
name: 'Hello World Plugin',
baseDataPath: 'options',
});
}
apply(compiler) {}
}
플러그인 개발 중 가장 중요한 두 개의 리소스는 compiler
와 compilation
객체입니다. 두 가지의 역할을 이해하는 것은 webpack 엔진을 확장하는 첫 번째 단계입니다.
class HelloCompilationPlugin {
apply(compiler) {
// callback 함수에 인자로 compilation을 제공하는 훅으로 compliation 훅을 탭 합니다.
compiler.hooks.compilation.tap('HelloCompilationPlugin', (compilation) => {
// 이제 컴파일을 통해 이용할 수 있는 다양한 훅들을 이용할 수 있습니다.
compilation.hooks.optimize.tap('HelloCompilationPlugin', () => {
console.log('Assets are being optimized.');
});
});
}
}
module.exports = HelloCompilationPlugin;
compiler
, compilation
그리고 다른 중요한 객체에서 사용 가능한 훅의 목록은 플러그인 API 문서를 참고해 주세요.
일부 플러그인 훅은 비동기입니다. 이를 탭 하려면, 동기로 동작하는 tap
메소드를 사용하거나 비동기 메소드인 tapAsync
또는 tapPromise
메소드를 사용할 수 있습니다.
tapAsync
메소드를 사용하여 플러그인을 탭해야 할 경우 함수에서 마지막 인자로 제공되는 callback 함수를 호출해야 할 필요가 있습니다.
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync(
'HelloAsyncPlugin',
(compilation, callback) => {
// Do something async...
setTimeout(function () {
console.log('Done with async work...');
callback();
}, 1000);
}
);
}
}
module.exports = HelloAsyncPlugin;
플러그인에 탭 하기 위해 tapPromise
를 사용할 때, 동기식 작업이 완료될 때의 해석 promise를 반드시 반환해야 할 필요가 있습니다.
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapPromise('HelloAsyncPlugin', (compilation) => {
// 끝났을 때 해석을 반환해야 합니다..
return new Promise((resolve, reject) => {
setTimeout(function () {
console.log('Done with async work...');
resolve();
}, 1000);
});
});
}
}
module.exports = HelloAsyncPlugin;
일단 webpack compiler와 각각의 개별적인 compilations를 이해 할 수 있게 되면, 엔진 자체로 할 수 있는 가능성이 무궁무진해집니다. 기존 파일을 다시 포맷하거나 파생 파일을 만들거나 완전히 새로운 애셋을 만들 수 있습니다.
새로운 빌드 파일인 assets.md
를 생성하는 예제를 작성해봅시다. 내용은 빌드에 있는 모든 애셋 파일을 나열합니다. 이 플러그인은 다음과 같습니다.
class FileListPlugin {
static defaultOptions = {
outputFile: 'assets.md',
};
// 어떤 옵션이 플러그인의 생성자에 전달될 때,
// (이는 플러그인의 public API입니다.)
constructor(options = {}) {
// 기본 옵션에 대해 사용자 지정 옵션을 적용하고
// 병합된 옵션을 플러그인 메소드에 추가로 사용할 수 있게 합니다.
// 여기서 모든 옵션을 확인해야 합니다.
this.options = { ...FileListPlugin.defaultOptions, ...options };
}
apply(compiler) {
const pluginName = FileListPlugin.name;
// webpack 모듈 인스턴스는 컴파일러 객체로부터 접근할 수 있으며,
// 이는 사용되는 모듈의 정확한 버전을 보장합니다.
// (webpack 또는 직접적으로 어떤 심볼을 require/import 하지 말아야 합니다.)
const { webpack } = compiler;
// Compilation 객체는 몇 가지 유용한 상수에 대한 참조를 제공합니다.
const { Compilation } = webpack;
// Rawsource는 compilation에서 애셋 소스를 나타내는데
// 사용해야 하는 "sources" 클래스 중 하나입니다.
const { RawSource } = webpack.sources;
// 이전 단계에서 compilation 프로세스를 추가로 탭하기 위해서는
// "thisCompilation" 훅을 탭해야합니다.
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
// 특정 단계에서 애셋 처리 파이프라인으로 탭합니다.
compilation.hooks.processAssets.tap(
{
name: pluginName,
// 이후 애셋 처리 단계 중 하나를 사용하여
// 모든 애셋이 이미 다른 플러그인에 의해 compliation에 추가되었는지 확인합니다.
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
},
(assets) => {
// "assets"는 compilation의 모든 애셋을 포함하는 객체이고,
// 객체의 key는 애셋의 경로 이름입니다.
// 그리고 values 파일 소스입니다.
// 모든 애셋을 거쳐 반복하고
// 마크다운 파일로 내용을 생성합니다.
const content =
'# In this build:\n\n' +
Object.keys(assets)
.map((filename) => `- ${filename}`)
.join('\n');
// 출력 디렉터리 webpack에 의해 자동으로 생성되도록
// compilation에 새로운 애셋을 추가합니다.
compilation.emitAsset(
this.options.outputFile,
new RawSource(content)
);
}
);
});
}
}
module.exports = { FileListPlugin };
webpack.config.js
const { FileListPlugin } = require('./file-list-plugin.js');
// 플러그인에 webpack 설정을 사용합니다.
module.exports = {
// …
plugins: [
// 기본 옵션을 사용하여 플러그인을 추가합니다.
new FileListPlugin(),
// 또는
// 지원되는 옵션을 전달하도록 선택할 수 있습니다.
new FileListPlugin({
outputFile: 'my-assets.md',
}),
],
};
이렇게 하면 다음과 같은 이름을 가진 마크다운 파일이 생성됩니다.
# In this build:
- main.css
- main.js
- index.html
플러그인은 탭 하는 이벤트 훅에 따라 타입으로 분류될 수 있습니다. 모든 이벤트 훅은 동기 또는 비동기 waterfall 또는 병렬 훅으로 사전에 정의되어 있고 훅은 내부적으로 call/callAsync 메소드를 사용하여 호출됩니다. 지원되거나 탭이 된 훅의 목록은 일반적으로 this.hooks
프로퍼티에 명시되어 있습니다.
예시는 다음과 같습니다.
this.hooks = {
shouldEmit: new SyncBailHook(['compilation']),
};
이것은 SyncBailHook
타입의 훅인 shouldEmit
만이 지원되는 훅이고 shouldEmit
훅에 탭 되는 플러그인에 전달되는 유일한 파라미터는 compilation
입니다.
지원되는 다양한 훅의 타입은 다음과 같습니다.
SyncHook
new SyncHook([params])
로 정의됩니다.tap
메소드를 사용하여 탭 됩니다.call(...params)
메소드를 사용하여 호출됩니다.Bail Hooks
SyncBailHook[params]
로 정의됩니다.tap
메소드를 사용하여 탭 됩니다.call(...params)
메소드를 사용하여 호출됩니다.Bail Hook 타입의 훅들은, 각각의 callback 플러그인이 특정한 args
를 사용하여 차례로 호출됩니다. 만약 플러그인에 의해 정의되지 않은 값을 제외하고 값이 반환된다면 훅에 의해 값이 반환되고 추가 플러그인 callback은 호출되지 않습니다. 많은 유용한 optimizeChunks
, optimizeChunkModules
와 같은 이벤트는 SyncBailHooks입니다.
Waterfall Hooks
SyncWaterfallHook[params]
로 정의됩니다.tap
메소드를 사용하여 탭 됩니다.call(...params)
메소드를 사용하여 호출됩니다.여기서 각각 플러그인은 이전 플러그인의 반환 값으로부터 인자들을 차례로 호출합니다. 플러그인은 실행 순서를 반드시 고려해야합니다.
실행된 이전의 플러그인으로부터 인자들을 반드시 허용해야합니다. 첫 번째 플러그인의 값은 init
입니다. 따라서 waterfall hooks에는 최소 1개의 parameter를 제공해야 합니다. 이 패턴은 ModuleTemplate
, ChunkTemplate
등과 같은 webpack 템플릿과 관련된 탭 될 수 있는 인스턴스에서 사용됩니다.
Async Series Hook
AsyncSeriesHook[params]
로 정의됩니다.tap
/tapAsync
/tapPromise
메소드를 사용하여 탭 됩니다.callAsync(...params)
메소드를 사용하여 호출됩니다.플러그인 핸들러 함수는 (err?: Error) -> void
서명이 있는 callback 함수와 모든 인자들이 호출됩니다. 핸들러 함수는 등록된 순서대로 호출 됩니다. callback
은 모든 핸들러가 호출되고 난 뒤 호출됩니다.
Async Series Hook은 또한 emit
, run
과 같은 이벤트에 흔히 사용되는 패턴입니다.
Async waterfall 플러그인은 waterfall 방식으로 비동기식으로 적용될 것입니다.
AsyncWaterfallHook[params]
로 정의됩니다.tap
/tapAsync
/tapPromise
메소드를 사용하여 탭 됩니다.callAsync(...params)
메소드를 사용하여 호출됩니다.플러그인 핸들러 함수는 (err: Error, nextValue: any) -> void.
서명이 있는 callback 함수와 현재 값으로 호출됩니다. nextValue
가 호출되면 다음 핸들러의 현재 값이 됩니다. 첫 번째 핸들러의 현재 값은 init
입니다. 모든 핸들러가 적용된 후 callback은 마지막 값으로 호출됩니다. 만약 어떤 핸들러가 err
값을 전달하면 callback은 오류가 호출되고 더이상 핸들러는 호출되지 않습니다.
이 플러그인 패턴은 before-resolve
와 after-resolve
와 같은 이벤트에서 사용할 수 있습니다.
Async Series Bail
AsyncSeriesBailHook[params]
로 정의됩니다.tap
/tapAsync
/tapPromise
메소드를 사용하여 탭 됩니다.callAsync(...params)
메소드를 사용하여 호출됩니다.Async Parallel
AsyncParallelHook[params]
로 정의됩니다.tap
/tapAsync
/tapPromise
메소드를 사용하여 탭 됩니다.callAsync(...params)
메소드를 사용하여 호출됩니다.플러그인의 기본값이 적용된 후 webpack은 설정의 기본값을 적용합니다. 이를 통해 플러그인은 고유의 기본값을 제공하고 사전에 플러그인의 설정을 만드는 방법을 제공합니다.