Printable
AutomaticPrefetchPlugin
AutomaticPrefetchPlugin은 변경사항을 관찰하는 동안 이전 컴파일의 모든 모듈을 미리 검색하여 증가하는 빌드 시간을 개선합니다. 단일 모듈을 미리 검색하는 PrefetchPlugin 과 비교해보세요.
webpack.config.js
export default {
// ...
plugins: [new webpack.AutomaticPrefetchPlugin()],
};BannerPlugin
생성된 각 청크의 상단에 배너를 추가합니다.
import webpack from "webpack";
new webpack.BannerPlugin(banner);
// 또는
new webpack.BannerPlugin(options);Options
{
banner: string | function, // 배너를 문자열 또는 함수로 사용하면, 주석으로 감싸집니다.
raw: boolean, // true이면, 배너가 주석에 감싸지지 않습니다.
entryOnly: boolean, // true이면, 배너는 엔트리 청크에만 추가됩니다.
test: string | RegExp | [string, RegExp], // 테스트를 통과하는 모든 모듈이 포함됩니다.
include: string | RegExp | [string, RegExp], // 다음 조건 중 하나라도 일치하는 모든 모듈이 포함됩니다.
exclude: string | RegExp | [string, RegExp], // 다음 조건 중 하나라도 일치하는 모든 모듈이 제외됩니다.
footer?: boolean, // true이면, 배너는 컴파일이 끝날 때 배치됩니다.
stage?: number, // 배너를 주입해야 하는 컴파일 단계
}Usage
import webpack from "webpack";
// 문자열
new webpack.BannerPlugin({
banner: "hello world",
});
// 함수
new webpack.BannerPlugin({
banner: (yourVariable) => `yourVariable: ${yourVariable}`,
});
// 최소화 및 애셋 조작 후 배너 메시지를 추가합니다.
new webpack.BannerPlugin({
raw: true,
banner: "/* 배너는 문자열입니다 */",
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT,
});Placeholders
webpack 2.5.0 부터, placeholder는 배너 문자열로 평가됩니다.
import webpack from "webpack";
new webpack.BannerPlugin({
banner:
"fullhash:[fullhash], chunkhash:[chunkhash], name:[name], base:[base], query:[query], file:[file]",
});ChunksWebpackPlugin
The chunks-webpack-plugin creates HTML files with entry points and chunks relations to serve your webpack bundles. It is suitable with multi-page applications that contain multiple entry points.
Since webpack 4, SplitChunksPlugin offers the possibility to optimizes all chunks. It can be particularly powerful, because it means that chunks can be shared even between async and non-async chunks. See the webpack documentation of splitChunks.chunks for details.
splitChunks.chunks option can be set to automatically generate new chunks associated with an entry point. For example, entry points a.js and b.js share common code with the file vendors~a~b.js.
With multiple entry points, it can be difficult to identify relation between the auto-generated chunks and entry points.
chunks-webpack-plugin parses the webpack compilation entry points to get all files associated with the entry points. Then, it generates HTML files which include all assets filtered by an entry point and thechunks-manifest.json file.
Zero configuration
It works without configuration. For advanced usage, see the using configuration section.
Installation
chunks-webpack-plugin is available on npm as chunks-webpack-plugin and as chunks-webpack-plugin on GitHub.
npm install chunks-webpack-plugin --save-devyarn add chunks-webpack-plugin --dev[!WARNING] Plugin
chunks-webpack-plugin@10is ESM only. [!NOTE] Minimum supportedNode.jsversion is16.20.0and Webpack>=5.10.3.
Example
The project includes a minimalist example in the ./example directory. Run the npm run build:example command to execute the Webpack example and see the plugin's implementation in action.
Basic usage
chunks-webpack-plugin will generate two HTML files for each entry point. Each filename contains the entry point name, the {{entry}} placeholder is automatically replaced.
{{entry}}-styles.html: contains all HTML<link>tags{{entry}}-scripts.html: contains all HTML<script>tags
First, let's add the plugin to the webpack configuration.
webpack.config.js
import ChunksWebpackPlugin from 'chunks-webpack-plugin';
export default {
plugins: [new ChunksWebpackPlugin()]
};HTML files are built in the output path directory with the rest of the webpack compilation.
Now you can include the generated HTML files into your HTML page templates. You can do it with e.g. Twig.
main-styles.html
<link rel="stylesheet" href="main.css" />main-scripts.html
<script defer src="main.js"></script>Using a configuration
You can pass a configuration object to chunks-webpack-plugin to override the default settings.
filename
Type:
type filename = string;Default: '[name]-[type].html'
Tells the plugin whether to personalize the filename of the generated files. Files are processed by the webpack compilation and generated in the output path directory. The placeholder [name] is automatically replaced by entry points names and [type] by styles|scripts.
new ChunksWebpackPlugin({
filename: 'templates/[name]-[type].html'
});[!NOTE] The
filenamecan contain directories, which will be created automatically.
templateStyle
Type:
type templateStyle = (name: string, entryName: string) => string;Default:
(name) => `<link rel="stylesheet" href="${name}" />`;Tells the plugin whether to personalize the default template for the HTML <style> tags. For example, add additional attributes or a CDN prefix.
export default {
plugins: [
new ChunksWebpackPlugin({
templateStyle: (name) => `<link rel="stylesheet" href="https://cdn.domain.com${name}" />`
})
]
};templateScript
Type:
type templateScript = (name: string, entryName: string) => string;Default:
(name) => `<script defer src="${name}"></script>`;Tells the plugin whether to personalize the default template for the HTML <script> tags. For example, add additional attributes or a CDN prefix.
export default {
plugins: [
new ChunksWebpackPlugin({
templateScript: (name) => `<script defer src="https://cdn.domain.com${name}"></script>`
})
]
};generateChunksManifest
Type:
type generateChunksManifest = boolean;Default: false
Tells the plugin whether to generate the chunks-manifest.json. The file contains the list of all chunks grouped by entry points. See the chunks-manifest.json example.
export default {
plugins: [
new ChunksWebpackPlugin({
generateChunksManifest: true
})
]
};generateChunksFiles
Type:
type generateChunksFiles = boolean;Default: true
Tells the plugin whether to generate the HTML files.
export default {
plugins: [
new ChunksWebpackPlugin({
generateChunksFiles: false
})
]
};[!WARNING] When set to
false, HTML files will not be generated. It can only be useful together withgenerateChunksManifestoption set totruefor custom generation of the HTML files.
Multiple entrypoints example
Multiple entrypoints example
Example of the webpack configuration with multiple entry points which share common code with the splitChunks option.
import ChunksWebpackPlugin from 'chunks-webpack-plugin';
import path from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: {
home: 'home.js',
news: 'news.js'
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
plugins: [new ChunksWebpackPlugin()],
optimization: {
splitChunks: {
chunks: 'all'
}
}
};The plugin will generate all files in the output path directory:
home-styles.html
<link rel="stylesheet" href="vendors~home~news.css" />
<link rel="stylesheet" href="home.css" />home-scripts.html
<script defer src="vendors~home~news.js"></script>
<script defer src="home.js"></script>news-styles.html
<link rel="stylesheet" href="vendors~home~news.css" />
<link rel="stylesheet" href="news.css" />news-scripts.html
<script defer src="vendors~home~news.js"></script>
<script defer src="news.js"></script>License
chunks-webpack-plugin is licensed under the MIT License.
Created with ♥ by @yoriiis.
CommonsChunkPlugin
CommonsChunkPlugin은 여러 엔트리 포인트 간에 공유되는 공통 모듈로 이루어진 별도의 파일(청크라고 합니다)을 만드는 opt-in 기능입니다.
번들에서 공통 모듈을 분리한 결과로 생성된 청크 파일을 처음에 한 번 로드하고 나중에 사용하기 위해 캐시에 저장할 수 있습니다. 이로 인해 브라우저가 새 페이지를 방문할 때마다 더 큰 번들을 로드하지 않고 캐시에서 공유 코드를 빠르게 제공할 수 있으므로 페이지 속도가 최적화됩니다.
new webpack.optimize.CommonsChunkPlugin(options);Options
{
name: string, // 또는
names: string[],
// 공통 청크의 청크 이름입니다. 청크의 이름을 전달하여 기존 청크를 선택할 수 있습니다.
// 문자열 배열로 전달되면 각 청크 이름에 대해 플러그인을 여러 번 호출하는 것과 같습니다.
// 생략하면 `options.async` 또는 `options.children`이 설정되고 모든 청크가 사용됩니다.
// 그렇지 않으면 청크 이름으로 사용되는 `options.filename`이 사용되게 됩니다.
// `options.async`를 사용하여 다른 비동기 청크에서 공통 청크를 만들 때 `option.name`을 생략하는 대신 여기에
// 엔트리 포인트 청크 이름을 지정해야 합니다.
filename: string,
// 공통 청크의 파일 이름 템플릿입니다. `output.filename`과 동일한 플레이스홀더를 포함할 수 있습니다.
// 생략하면 원본 파일 이름은 수정되지 않습니다(일반적으로 `output.filename` 혹은 `output.chunkFilename`).
// 이 옵션은 `options.async`를 사용하는 경우에도 허용되지 않습니다. 자세한 내용은 아래를 참조하세요.
minChunks: number|Infinity|function(module, count) => boolean,
// 공통 청크로 이동하기 전에 모듈을 포함해야 하는 최소 청크 수입니다.
// 숫자는 2보다 크거나 같고 청크 수보다 작거나 같아야 합니다.
// `Infinity`를 전달하면 공통 청크가 생성되지만 모듈은 이동하지 않습니다.
// `function`을 제공하여 맞춤 로직을 추가 할 수 있습니다.(기본값은 청크의 수입니다)
chunks: string[],
// 청크 이름으로 소스 청크를 선택합니다. 청크는 공통 청크의 자식이어야 합니다.
// 생략하면 모든 엔트리 청크가 선택됩니다.
children: boolean,
// `true`이면 공통 청크의 모든 자식이 선택됩니다.
deepChildren: boolean,
// `true`이면 공통 청크의 모든 자손이 선택됩니다.
async: boolean|string,
// `true`이면 새로운 비동기 공통 청크가 `options.name`의 하위 항목 및 `options.chunks`와 동등한 항목으로 생성됩니다.
// `options.chunks`와 병렬로 로드됩니다.
// `option.filename`을 사용하는 대신 `true`가 아닌 원하는 문자열을 제공해
// 출력 파일의 이름을 변경할 수 있습니다.
minSize: number,
// 공통 청크가 생성되기 전 모든 공통 모듈의 최소 사이즈입니다.
}Examples
Commons chunk for entries
엔트리 포인트 간에 공유되는 공통 모듈을 포함하는 추가 청크를 생성합니다.
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
// (공통 청크 이름)
filename: "commons.js",
// (공통 청크의 파일 이름)
// minChunks: 3,
// (모듈은 최소 3개의 엔트리 사이에서 공유해야 합니다)
// chunks: ["pageA", "pageB"],
// (이 엔트리에서만 사용됩니다)
});생성된 청크는 엔트리 포인트 전에 로드해야 합니다.
<script src="commons.js" charset="utf-8"></script>
<script src="entry.bundle.js" charset="utf-8"></script>Explicit vendor chunk
코드를 vendor와 애플리케이션으로 분할합니다.
module.exports = {
// ...
entry: {
vendor: ["jquery", "other-lib"],
app: "./entry",
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// filename: "vendor.js"
// (청크에 다른 이름을 지정하세요)
minChunks: Infinity,
// (엔트리가 더 많으면 다른 모듈이 vendor 청크로
// 들어가지 않습니다)
}),
],
};<script src="vendor.js" charset="utf-8"></script>
<script src="app.js" charset="utf-8"></script>Move common modules into the parent chunk
Code Splitting을 사용하면 엔트리 청크의 여러 하위(자식) 청크가 공통 종속성을 가질 수 있습니다. 중복을 방지하기 위해 이들을 부모 청크로 옮길 수 있습니다. 이렇게 하면 전체 크기가 줄어들지만, 초기 로드 시간에 부정적인 영향을 미칩니다. 사용자가 많은 형제 청크(예: 엔트리 청크의 자식)를 다운로드해야 할 것으로 예상되는 경우 전체 로드 시간이 향상됩니다.
new webpack.optimize.CommonsChunkPlugin({
// names: ["app", "subPageA"]
// (청크를 선택하거나 모든 청크를 생략하세요)
children: true,
// (선택된 청크의 모든 자식을 선택합니다)
// minChunks: 3,
// (모듈을 옮기기 전에 3명의 자식이 공유해야 합니다)
});Extra async commons chunk
위의 것과 유사하지만 공통 모듈을 부모로 이동하는 대신(초기 로드 시간을 증가시키게 됩니다) 비동기식으로 로드된 새로운 추가 공통 청크가 사용됩니다. 이는 추가 청크가 다운로드되면 자동으로 병렬로 다운로드됩니다.
new webpack.optimize.CommonsChunkPlugin({
name: "app",
// 또는
names: ["app", "subPageA"],
// 단일 이름 또는 배열 형태의 이름은 비동기 청크를 생성하는
// 엔트리 포인트의 이름과 일치해야 합니다.
children: true,
// (청크의 모든 자식을 사용합니다)
async: true,
// (비동기 공통 청크를 생성합니다)
minChunks: 3,
// (모듈이 분리되기 전에 3명의 자식이 모듈을 공유해야 합니다)
});Passing the minChunks property a function
또한 minChunks 속성에 함수를 전달할 수도 있습니다. 이 함수는 CommonsChunkPlugin에 의해 호출되며 module 및 count 인수로 함수를 호출합니다.
module 인수는 name/names 속성을 통해 제공된 청크의 각 모듈을 나타냅니다.
module은 NormalModule의 형태로, 이 사용 사례에 특히 유용한 두 개의 속성이 있습니다.
module.context: 파일을 저장하는 디렉터리입니다. 예:'/my_project/node_modules/example-dependency'module.resource: 처리중인 파일의 이름입니다. 예:'/my_project/node_modules/example-dependency/index.js'
count 인수는 module이 사용되는 청크 수를 나타냅니다.
이 옵션은 CommonsChunk 알고리즘을 통해 모듈을 이동할 위치를 결정하는 방법을 세밀하게 제어하려는 경우에 유용합니다.
new webpack.optimize.CommonsChunkPlugin({
name: "my-single-lib-chunk",
filename: "my-single-lib-chunk.js",
minChunks(module, count) {
// 모듈이 경로를 가지고 있고 경로에 "somelib"이 존재하고
// 3개의 개별 청크/엔트리에서 사용되는 경우
// 청크의 키 이름이 "my-single-lib-chunk", 파일 이름이 "my-single-lib-chunk.js"로 분할됩니다.
return module.resource && /somelib/.test(module.resource) && count === 3;
},
});위에서 볼 수 있듯이 이 예제를 사용하면 함수 내의 모든 조건이 충족되는 경우에만, 하나의 라이브러리를 별도의 파일로 이동할 수 있습니다.
이 개념은 암시적 공통 vendor 청크를 얻는데 사용할 수 있습니다.
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks(module) {
// 이는 vendor의 import가 node_modules 디렉터리에 존재한다고 가정합니다.
return module.context && module.context.includes("node_modules");
},
});Manifest file
webpack 부트스트랩 로직을 별도의 파일로 추출하려면 entry로 정의되어 있지 않은 name에 CommonsChunkPlugin을 사용하세요. 자세한 내용은 캐싱 가이드를 참조하세요.
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity,
});Combining implicit common vendor chunks and manifest file
vendor와 manifest 청크는 minChunks에 대해 다른 정의를 사용하므로 플러그인을 두 번 호출해야 합니다.
[
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks(module) {
return module.context && module.context.includes("node_modules");
},
}),
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity,
}),
];More Examples
ContextExclusionPlugin
Context는 require('./locale/' + name + '.json')와 같은 import() 또는 require()의 동적 표현식을 의미합니다.
ContextExclusionPlugin을 사용하면 컨텍스트를 제외할 수 있습니다. 정규 표현식과 일치하는 모든 컨텍스트를 제외하려면 플러그인을 초기화할 때 정규 표현식을 인수로 제공하세요.
webpack.config.js
export default {
plugins: [new webpack.ContextExclusionPlugin(/dont/)],
};ContextReplacementPlugin
Context는 require('./locale/' + name + '.json')와 같은 import() 또는 require()의 동적 표현식을 의미합니다. 이러한 표현식을 만나면 webpack은 디렉터리(./locale/')와 정규 표현식(/^.*\.json$/)을 추론합니다. 컴파일 시점에 name을 알 수 없으므로 webpack은 모든 파일을 모듈로 번들에 포함합니다.
ContextReplacementPlugin을 사용하면 추론된 정보를 오버라이드 할 수 있습니다. 플러그인을 구성하는 방법에는 여러 가지가 있습니다.
Usage
new webpack.ContextReplacementPlugin(
resourceRegExp: RegExp,
newContentResource?: string,
newContentRecursive?: boolean,
newContentRegExp?: RegExp
)리소스(디렉터리)가 resourceRegExp와 일치하면, 플러그인은 기본 리소스, 재귀 플래그 또는 생성된 정규식을 각각 newContentResource, newContentRecursive 또는 newContentRegExp로 바꿉니다. newContentResource가 상대적인 경우 이전 리소스를 기준으로 해결됩니다.
다음은 모듈 사용을 제한하는 작은 예제입니다.
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /de|fr|hu/);moment/locale 컨텍스트는 /de|fr|hu/와 일치하는 파일로 제한됩니다. 따라서 해당 locale만 포함됩니다(자세한 내용은 이 이슈를 참고하세요).
Content Callback
new webpack.ContextReplacementPlugin(
resourceRegExp: RegExp,
newContentCallback: (data) => void
);newContentCallback 함수에는 ContextModuleFactory의 data 객체가 제공되며 제공된 객체의 request 속성을 덮어쓸 것으로 예상됩니다.
이 콜백을 사용하여 요청을 새 위치로 동적 리다이렉션할 수 있습니다.
new webpack.ContextReplacementPlugin(/^\.\/locale$/, (context) => {
if (!/\/moment\//.test(context.context)) return;
Object.assign(context, {
regExp: /^\.\/\w+/,
request: "../../locale", // 상대적으로 해결
});
});Other Options
newContentResource 및 newContentCreateContextMap 파라미터도 사용할 수 있습니다.
new webpack.ContextReplacementPlugin(
resourceRegExp: RegExp,
newContentResource: string,
newContentCreateContextMap: object // 런타임-요청(userRequest)을 컴파일-시간-요청(request)으로 매핑
);이 두 파라미터를 함께 사용하여 보다 대상화된 방식으로 요청을 리다이렉션할 수 있습니다. newContentCreateContextMap을 사용하면 런타임 요청을 매핑하여 객체 형태로 요청을 컴파일 할 수 있습니다.
new ContextReplacementPlugin(/selector/, "./folder", {
"./request": "./request",
"./other-request": "./new-request",
});CopyPlugin
빌드 과정에서 파일과 디렉터리를 output.path로 복사합니다. 이 plugin은 output.copy의 기반이며, 해당 옵션을 설정하면 webpack이 자동으로 적용합니다. output.copy에서 제공하지 않는 concurrency 또는 stage 옵션이 필요할 때 plugin을 직접 사용하세요.
import webpack from "webpack";
new webpack.CopyPlugin(options);Options
{
patterns: (string | object)[], // 복사할 항목, output.copy 참고
concurrency?: number, // 동시에 읽을 파일 수
stage?: number, // 파일을 복사할 processAssets 단계
}patterns((string | object)[]): 복사할 파일입니다. 패턴은from과context,filename,globOptions,info,preservePermissions,preserveTimestamps,to,transform으로 구성됩니다. 동일한 패턴을 사용하는output.copy에서 모두 설명합니다.concurrency(number = 100): 동시에 읽을 수 있는 최대 파일 수입니다.stage(number = webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL): 파일을 복사할processAssets단계입니다. 기본 단계에서는 복사된 파일도 다른 애셋과 마찬가지로 최소화되고 압축됩니다. 이후 단계를 지정하면 이러한 작업을 수행할 tap이 끝난 뒤 파일을 emit합니다.
Usage
import webpack from "webpack";
export default {
// ...
plugins: [
new webpack.CopyPlugin({
patterns: [
"static",
{ from: "img", to: "images" },
{ from: "licenses/*.txt", to: "licenses" },
],
concurrency: 50,
}),
],
};Keeping a file out of the minimizer
미리 빌드된 파일은 이미 최소화되어 있으므로 minimizer를 다시 실행하면 불필요하게 시간만 소요됩니다. asset info에 최소화된 파일이라고 표시하면 minimizer가 해당 파일을 건너뜁니다.
import webpack from "webpack";
export default {
// ...
plugins: [
new webpack.CopyPlugin({
patterns: [
{
from: "*.min.js",
context: "vendor",
to: "vendor",
info: { minimized: true },
},
],
}),
],
};Merging copied assets
하나의 소스 파일은 하나의 애셋이 되므로 여러 파일을 하나로 합치려면 복사된 결과물을 다시 처리해야 합니다. 이 작업은 어떤 plugin으로도 수행할 수 있습니다. 복사된 애셋의 asset info에는 copied: true가 포함되므로 다음과 같이 대상을 선별할 수 있습니다.
compilation.hooks.processAssets.tap(
{
name: "MergeCopiedAssetsPlugin",
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED,
},
() => {
const assets = compilation
.getAssets()
.filter((asset) => asset.info.copied && /^licenses\//.test(asset.name))
.toSorted((a, b) => (a.name < b.name ? -1 : 1));
if (assets.length === 0) return;
const merged = assets
.map((asset) => `/* ${asset.name} */\n${asset.source.source()}`)
.join("\n");
compilation.emitAsset(
"THIRD_PARTY_LICENSES.txt",
new webpack.sources.RawSource(merged),
{ copied: true },
);
for (const asset of assets) compilation.deleteAsset(asset.name);
},
);DefinePlugin
DefinePlugin을 사용하면 컴파일 타임에 구성할 수 있는 전역 상수를 만들 수 있습니다. 이는 개발 빌드와 프로덕션 빌드 간에 서로 다른 동작을 하고 싶을 때 유용합니다. 개발 빌드에서 로깅을 수행하지만, 프로덕션 빌드에서는 수행하지 않는 경우 전역 상수를 사용하여 로깅을 수행할지 여부를 결정할 수 있습니다. 이것이 DefinePlugin가 유용한 이유입니다. 이 플러그인을 설정하고 개발 및 프로덕션 빌드 환경을 잊어버리세요.
new webpack.DefinePlugin({
// Definitions...
});Usage
DefinePlugin에 전달된 각 키는 .로 결합 된 개별 또는 다중 식별자입니다.
- 값이 문자열이면 이는 코드 조각으로 사용됩니다.
- 값이 문자열이 아닌 경우 문자열화 됩니다(함수를 포함해서).
- 값이 객체라면 모든 키가 동일한 방식으로 정의됩니다.
- 키에
typeof접두사를 붙이면 typeof 호출에 대해서만 정의됩니다.
값은 코드에 인라인 되어 중복 조건을 제거하기 위한 최소화 단계를 거치게 됩니다.
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object"),
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
});console.log(`Running App version ${VERSION}`);
if (!BROWSER_SUPPORTS_HTML5) require("html5shiv");if (!PRODUCTION) {
console.log("Debug info");
}
if (PRODUCTION) {
console.log("Production log");
}최소화 단계 없이 webpack을 통과하면 다음과 같은 결과가 나타납니다.
if (!true) {
console.log("Debug info");
}
if (true) {
console.log("Production log");
}그 후 최소화 단계를 거친 결과는 다음과 같습니다.
console.log("Production log");Feature Flags
기능 플래그를 사용하여 프로덕션/개발 빌드 기능을 활성화/비활성화 할 수 있습니다.
new webpack.DefinePlugin({
NICE_FEATURE: JSON.stringify(true),
EXPERIMENTAL_FEATURE: JSON.stringify(false),
});Service URLs
프로덕션/개발 빌드에서 서로 다른 서비스 URL을 사용할 수 있습니다.
new webpack.DefinePlugin({
SERVICE_URL: JSON.stringify("https://dev.example.com"),
});Runtime values via runtimeValue
(fn: (context: { module: NormalModule, key: string, version: string | undefined }) => CodeValuePrimitive, options?: true | string[] | RuntimeValueOptions) => RuntimeValue
파일에 의존하는 값으로 변수를 정의할 수 있으며 이러한 파일이 파일 시스템에서 변경 될 때 재평가 됩니다. 이는 감시된 파일이 변경될 때 webpack이 재빌드 됨을 의미합니다.
webpack.DefinePlugin.runtimeValue 함수에는 두 가지 인자가 있습니다.
- 첫 번째 인자는 위의 제너레이터 함수입니다.
{ module, key, version }을 받아 정의에 할당할 값을 반환합니다. - 두 번째 인자는 감시할 파일 경로의 배열이거나 모듈을 캐시 할 수 없음으로 표시하는
true플래그일 수 있습니다. 5.26.0 버전부터는 아래의 속성을 가진 객체 인자를 사용할 수 있습니다.fileDependencies?: string[]함수가 의존하는 파일 목록입니다.contextDependencies?: string[]함수가 의존하는 디렉터리 목록입니다.missingDependencies?: string[]함수가 의존하는 존재하지 않는 파일 목록입니다.buildDependencies?: string[]함수가 의존하는 빌드 디펜던시 목록입니다.version?: string | () => string함수의 버전입니다.
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const fileDep = path.resolve(__dirname, "sample.txt");
new webpack.DefinePlugin({
BUILT_AT: webpack.DefinePlugin.runtimeValue(Date.now, {
fileDependencies: [fileDep],
}),
});BUILT_AT의 값은 'sample.txt'가 파일 시스템에서 마지막으로 업데이트된 시간입니다. (예: 1597953013291)
DiagnosticsWebpackPlugin
This plugin only supports webpack
>= 5.106.0and Node.js>= 22.12.0.
This plugin runs linters, type checkers and other diagnostic tools over your sources during the webpack build and reports what they find as webpack errors and warnings.
It replaces eslint-webpack-plugin and stylelint-webpack-plugin: one plugin, one place to configure how problems are reported, and one pass over your project. Today it runs ESLint, Stylelint, oxlint, Biome and TypeScript; more linters and diagnostic tools are meant to be added the same way.
Getting Started
To begin, you'll need to install diagnostics-webpack-plugin:
npm install diagnostics-webpack-plugin --save-dev
or
yarn add -D diagnostics-webpack-plugin
or
pnpm add -D diagnostics-webpack-plugin
[!NOTE]
Install the tools you want to run as well — the plugin only requires the ones you enable. It supports
eslint >= 9,stylelint >= 17,oxlint >= 1,@biomejs/biome >= 2andtypescript >= 5:
npm install eslint stylelint oxlint @biomejs/biome typescript --save-dev
Then add the plugin to your webpack configuration and enable a check for each language you want inspected:
import DiagnosticsPlugin from "diagnostics-webpack-plugin";
export default {
// ...
plugins: [
new DiagnosticsPlugin({
checks: [
{ use: "eslint", extensions: ["js", "mjs"] },
{ use: "stylelint", extensions: ["css", "scss"] },
],
}),
],
// ...
};The package ships an ECMAScript build next to a CommonJS one, so a CommonJS configuration works just as well:
const DiagnosticsPlugin = require("diagnostics-webpack-plugin");Options
The plugin options have three layers:
| Layer | Where it goes | What it covers |
|---|---|---|
| Plugin | Top level only | How the plugin schedules its work, for every check at once. |
| Shared | Top level or in a checks entry | Which files are linted and how problems are reported. An entry overrides what it sets. |
| Check | In a checks entry | Options only that tool understands, plus everything its own Node.js API accepts. |
The options are checked against their schema from webpack's own validate hook, so a mistake is reported when webpack validates the rest of your configuration, and validate: false turns the check off along with webpack's.
Every check to run is an entry in checks, named by its use. The list may name the same tool more than once, so one instance can inspect two file sets under different configurations.
Every option
| Option | Layer | What it decides |
|---|---|---|
checks | Plugin | Which checks run, and the options only each of them understands. |
context | Plugin | The folder every relative files and exclude pattern is resolved against. |
lintOnStart | Plugin | Whether the first compilation checks everything it covers. |
cache | Shared | Whether the tool keeps a cache of its own between runs. |
cacheLocation | Shared | Where that cache is written. |
exclude | Shared | What is left out. |
extensions | Shared | Which extensions a named folder is walked for. |
files | Shared | What to check: naming it checks every file it matches, built or not. |
fix | Shared | Whether the tool writes back what it can fix. |
formatter | Shared | How results are turned into the message that is reported. |
outputReport | Shared | A file the same results are written to. |
reportAs | Shared | What the build carries: an error, a warning, a log line, or nothing. |
resourceQueryExclude | Shared | Which module queries are left out. |
threads | Shared | How wide the work is spread. |
configType, eslintPath | Check | ESLint's own. |
stylelintPath | Check | Stylelint's own. |
oxlintPath, configFile, args | Check | oxlint's own. |
biomePath, command, configFile, args | Check | Biome's own. |
typescriptPath, configFile, compilerOptions, build, diagnosticOptions, ignoreDiagnostics | Check | TypeScript's own. |
Anything else written in a checks entry is handed to the tool itself, so its
own Node.js API options go next to these.
new DiagnosticsPlugin({
// Plugin options
context: "src",
// Shared options, every check uses them unless it says otherwise
reportAs: "warning", // report every check's results as warnings
exclude: ["node_modules", "vendor"],
// The checks to run, each with the options only it understands
checks: [
{ use: "eslint", extensions: ["js", "ts"], fix: true },
{ use: "stylelint", extensions: ["css", "scss"], threads: true },
],
});Plugin options
checks
- Type:
type checks = (string | ({ use: string | CheckAdapter } & Options))[];- Default: none — this is the one option the plugin requires
The checks to run. An entry is the name of a built-in check, or an object naming
it under use next to the options it takes, so these two are the same thing:
new DiagnosticsPlugin({ checks: ["eslint", { use: "eslint" }] });Naming the same tool twice runs it twice, which is how one build checks two file sets under two configurations:
new DiagnosticsPlugin({
checks: [
{ use: "eslint", files: "src", fix: true },
{ use: "eslint", files: "scripts", reportAs: "warning" },
],
});use also takes an adapter of your own rather than a built-in name — see
Adding a check.
context
- Type:
type context = string;- Default:
compiler.context
Base directory for linting. Every relative files and exclude pattern is resolved against it.
lintOnStart
- Type:
type lintOnStart = boolean;- Default:
true
Whether the first compilation lints every file it covers. Leave it alone and a build lints everything while a watch run lints everything once and then only what webpack rebuilds.
Set it to false to start a watch run quiet: nothing is linted until you touch
a file, and only the modules webpack rebuilds are reported. A build is nothing
but a first compilation, so it lints either way — the option cannot silence one.
Running the checks in one mode only
There is no option for this and none is needed: plugins ignores a falsy entry,
so && decides whether the plugin is there at all. A configuration function is
handed the --env values, and webpack-cli states the mode in them:
| Value | Set by |
|---|---|
WEBPACK_SERVE | webpack serve |
WEBPACK_WATCH | webpack watch and webpack --watch |
WEBPACK_BUILD, WEBPACK_BUNDLE | webpack, a build that runs once |
import DiagnosticsPlugin from "diagnostics-webpack-plugin";
import { defineConfig } from "webpack";
export default defineConfig((env) => ({
plugins: [
// Both values are read because each command sets only its own: `serve` sets
// `WEBPACK_SERVE` and `--watch` sets `WEBPACK_WATCH`, so asking for one of
// them misses the other way of watching. Read `WEBPACK_BUILD` instead to
// check a build that runs once and leave a watch run alone.
(env.WEBPACK_SERVE || env.WEBPACK_WATCH) &&
new DiagnosticsPlugin({ checks: ["eslint", "stylelint"] }),
],
}));This is worth doing when something else already lints the same files — a CI job
running eslint ., or an editor — and the release build need not pay for it
twice.
Two configurations at once
A client and a server configuration lint the same sources twice. Where two compilers run a check configured the same way over the same files — the whole of it for a check reading from the file system, whatever they both built for one reading the module graph — the second joins the run the first is making rather than starting its own, and both compilations report what it finds and watch what it read.
Over three hundred files with the typescript check, two compilers build one
program rather than two: 1.52 s rather than 1.79 s, and 248 MB rather than
298 MB. What a compiler checks alone it still checks alone, and a check
configured differently in the second configuration runs on its own.
Shared options
These can be set at the top level, where they apply to every check, or inside one check, where they apply to that check alone.
cache
- Type:
type cache = boolean;- Default:
true
The cache is enabled by default to decrease execution time.
It is turned off for an ESLint check doing typed linting: a cache keyed on one file's own contents cannot answer for a rule that read another.
cacheLocation
- Type:
type cacheLocation = string;- Default:
node_modules/.cache/diagnostics-webpack-plugin/.<tool>cache
Specify the path to the cache location. Can be a file or a directory.
files
- Type:
type files = string | string[];- Default:
options.context
Specify directories, files, or globs. Must be relative to options.context.
Directories are traversed recursively looking for files matching options.extensions.
File and glob patterns ignore options.extensions.
Naming them says what to check, so every file they match is checked whether or not webpack built it — a module nothing imports yet, or one reached through a loader webpack resolves differently, is checked all the same. Leave it unset and a check reads whatever it reads by itself: ESLint the modules webpack built, Stylelint a walk of the context.
In a watch run the folder a check takes its files from is watched, so a file
you add there is checked without anything else having to change — and for the
typescript check, so is every folder the tsconfig.json include covers. A
folder is only watched when the whole of it can be: webpack rebuilds on a
change anywhere under one, so a folder holding what the build writes
(output.path) or what the check itself leaves out
(exclude, node_modules by default) is left alone rather than
turning every write into a rebuild. Pointing files at your sources rather
than leaving it at the project root is what makes it watchable.
extensions
- Type:
type extensions = string | string[];- Default:
'js'for ESLint,['css', 'scss', 'sass']for Stylelint
Specify file extensions that should be checked.
exclude
- Type:
type exclude = string | string[];- Default:
'node_modules', plusoutput.pathfor Stylelint
Specify the files/directories to exclude. Must be relative to options.context.
threads
- Type:
type threads = boolean | number | "auto";- Default:
"auto"
How many threads a check spreads its work over. "auto" takes one fewer than the
machine has, leaving webpack the thread it builds on; a number asks for that
many; false, 0 or 1 keeps the work on webpack's own thread.
A check that threads its own work is asked to, and one that cannot is run in a pool of workers — so the option means the same thing whatever is being checked, and a check added later gets it for nothing. ESLint threads its own work from 9.34.0 under flat config; Stylelint threads none of its own, so it is pooled. TypeScript reads a program whole rather than a file at a time, so it is run on one worker: what a thread buys it is webpack's own thread back.
Prefer "auto" over a count. A check sizes "auto" against the machine, where a
fixed number can end up competing with webpack for the same cores: over three
hundred modules on four of them, "auto" took about a fifth off the build while
asking for three was no better than asking for none. A count above what the
machine has is held to it, since asking for sixteen threads on four cores took
twice as long as asking for none — the ceiling cannot save a number written
against the tool itself, such as ESLint's concurrency, which is passed through
as written.
Anything written against the tool itself wins — ESLint's own concurrency, say —
and a check configured with a function, such as a formatter written in the
configuration, is run on webpack's thread, because a function cannot be handed
to a worker.
resourceQueryExclude
- Type:
type resourceQueryExclude = RegExp | RegExp[] | string | string[];- Default:
[]
Specify the resource query to exclude. A string is read as the source of a regular expression, so "raw" and /raw/ mean the same thing. Only affects checks that read the module graph, such as ESLint.
fix
- Type:
type fix = boolean;- Default:
false
Will enable the autofix feature of the tool.
Be careful: this option will modify source files.
formatter
- Type:
type formatter = string | ((results: LintResult[]) => string);- Default: the tool's own default formatter
Accepts the name of a formatter the tool ships, or a function that receives its results and returns the output as a string.
See the ESLint formatters and the Stylelint formatter option.
Errors and warnings
Every check reports its errors as webpack errors and its warnings as webpack warnings, which is what fails the build. reportAs overrides that.
reportAs
- Type:
type reportAs = Severity | { errors?: Severity; warnings?: Severity };
type Severity = "error" | "warning" | "log" | false;- Default: unset — each result stays at the severity the check gave it
What a check reports its results as. One value covers its errors and its warnings alike; an object sets them apart, and a severity the object leaves out keeps its own:
| Value | Effect |
|---|---|
| unset | Errors fail the build, warnings do not. |
"error" | Everything fails the build, warnings included. |
"warning" | Nothing fails the build; errors are reported as warnings. |
"log" | The terminal alone, through webpack's log. |
false | Nothing is reported. An outputReport is still written. |
{ warnings: false } | The errors alone, still failing the build. |
{ warnings: "error" } | Warnings fail the build too, and errors keep failing it. |
{ errors: "warning" } | Errors stop failing the build, and warnings stay warnings. |
new DiagnosticsPlugin({
reportAs: { warnings: false }, // the errors alone
checks: [{ use: "eslint" }],
});"log" is for a check nobody should be stopped by yet. Its results reach
webpack's log — the terminal, and stats.logging — rather than
compilation.errors or compilation.warnings, so the build carries neither,
stats.hasWarnings() stays false and a dev server overlays nothing. An
outputReport is written either way.
new DiagnosticsPlugin({
// the whole project is linted, and only the new check is advisory
checks: [{ use: "eslint" }, { use: "typescript", reportAs: "log" }],
});A check nothing can be reported from is one a watch rebuild does not wait
for: every reportAs that leaves both errors and warnings off the compilation
— "log", false, or an object spelling both out — with no
outputReport to write. The first build still waits, since it
is what tells the watcher which files the check reads. From the rebuild on, the
check is run once the build is over rather than beside it, and what it finds is
printed through webpack's infrastructure log rather than through
stats.logging.
The work is moved out of the rebuild rather than off the machine: an edit arriving while such a check is still running waits for the rest of it, since a check holds the thread the build runs on. A report a newer rebuild has already overtaken is dropped, and a check never has two of its runs going at once — the next starts when the last has reported.
ignoreDiagnostics
- Type:
type ignoreDiagnostics =
IgnoreOne | IgnoreOne[] | ((message: Message) => boolean);
type IgnoreOne = number | string | IgnoreMatch;
interface IgnoreMatch {
file?: string | undefined;
code?: string | undefined;
severity?: "error" | "warning" | undefined;
}
interface Message {
file?: string | undefined;
code?: string | undefined;
severity: "error" | "warning";
text: string;
}- Default: unset
What not to report. A linter's rule can be turned off in its own configuration and a TypeScript diagnostic cannot, so this is where one thing a check found is left out whichever check found it — for adopting a check on a project that is not clean yet, without turning the check off altogether.
| Written as | Leaves out |
|---|---|
2307 | The TypeScript code, as tsc prints it after TS. |
"no-unused-vars" | The rule or code a check names. |
{ file, code, severity } | What all three match; one left out matches everything. |
[…] | Anything the list matches. |
(message) => boolean | Whatever the function answers true for. |
new DiagnosticsPlugin({
checks: [
// TS2307 is `Cannot find module`, TS7016 an untyped dependency
{ use: "typescript", ignoreDiagnostics: [2307, 7016] },
// a rule that has not been dealt with in one folder yet
{
use: "eslint",
ignoreDiagnostics: { code: "no-unused-vars", file: "src/legacy/**" },
},
],
});A file is a glob matched against the path relative to
context, as exclude is. Something a check reported
of no file in particular — a configuration it could not read — is not a file's
to leave out, so a match naming one keeps it.
What is left out is left out of everything the check reports, an
outputReport included, and of the counts a formatter prints.
A check shipped outside this package answers for its own results, so it reads
this option once it says how (see Adding a check).
outputReport
- Type:
type outputReport =
| boolean
| {
filePath?: string | undefined;
formatter?: (string | ((results: LintResult[]) => string)) | undefined;
};- Default:
false
Write the results to a file, for example a checkstyle xml file for use for reporting on Jenkins CI.
filePath: path to the output report file, relative tooutput.pathunless absolute.formatter: a differentformatterfor the output file; the default/configured formatter is used when none is passed in.
Set at the top level, every check appends its report to the same file. Set it inside a checks entry to give that check a file of its own.
new DiagnosticsPlugin({
checks: [
{
use: "eslint",
outputReport: { filePath: "eslint.json", formatter: "json" },
},
{
use: "stylelint",
outputReport: { filePath: "stylelint.json", formatter: "json" },
},
],
});ESLint
Run with { use: "eslint" }. It lints the files webpack builds, so only the modules that end up in the bundle are checked.
Alongside the shared options you can pass any ESLint Node.js API option — they are handed to the ESLint class as they are.
threads reaches ESLint as its own concurrency from 9.34.0 under flat config, and an older ESLint or an eslintrc one is pooled instead. Write concurrency yourself and that is what ESLint is given, whatever threads says. Note that webpack spells this idea parallelism, and uses the word concurrency for something else again — bounded work on one thread.
A rebuild lints only the files webpack rebuilt and reports the rest from the previous run, so lintOnStart is only worth setting to start a watch run quiet.
Typed linting is the exception, and it is read from your configuration rather than asked of you. Where parserOptions.project or parserOptions.projectService is set, what a rule reports of one file can turn on another — a type it imported, a declaration file something regenerated — so a file that did not itself change is not one whose last result still holds. A rebuild then lints every file the check covers, and ESLint's own cache is turned off for it, since that cache is keyed on each file's own contents and would answer for a type it never read.
configType
- Type:
type configType = "flat" | "eslintrc";- Default:
flat
Specify the type of configuration to use with ESLint.
flatis the current standard configuration format.eslintrcis the legacy configuration format and has been officially deprecated.
The new configuration format is explained in its own documentation.
eslintrc needs an ESLint that still reads one: ESLint 9 does, ESLint 10 removed
it, and the option stays for as long as this plugin supports 9. The check loads
ESLint the same way wherever it runs it, in the build's own thread and in a
worker of a pool alike, and the ESLint 9 job in CI checks both.
eslintPath
- Type:
type eslintPath = string;- Default:
eslint
Path to the eslint instance that will be used for linting.
If the eslintPath is a folder like the official ESLint, or you specify a formatter option, you don't have to install eslint.
Suppressions
Bulk suppressions are supported: enable ESLint's own applySuppressions, and point suppressionsLocation at the file if it is not the default eslint-suppressions.json.
new DiagnosticsPlugin({
checks: [{ use: "eslint", applySuppressions: true }],
});[!IMPORTANT]
ESLint resolves the suppressions file, and every path recorded inside it, against its own
cwd— not against the plugin'scontext. Where the two differ, passcwdto the check as well:new DiagnosticsPlugin({ context: "src", checks: [ { use: "eslint", applySuppressions: true, cwd: import.meta.dirname }, ], });
Suppressions need ESLint 9.24 or later. ESLint 10 takes both options itself; below that they reach its CLI alone, so the plugin applies the suppressions after linting instead — the same file, the same paths, the same result.
Stylelint
Run with { use: "stylelint" }, and requires stylelint >= 17. It lints every file matching files and extensions on disk, whether or not webpack imported it, so a stylesheet nothing imports yet is still checked.
Alongside the shared options you can pass any Stylelint option — they are handed to stylelint.lint() as they are.
stylelintPath
- Type:
type stylelintPath = string;- Default:
stylelint
Path to the stylelint instance that will be used for linting.
oxlint
Run with { use: "oxlint" }, and requires oxlint >= 1. It lints the files
webpack builds, as the ESLint check does, and reads whatever .oxlintrc.json
oxlint finds for itself.
new DiagnosticsPlugin({ checks: ["eslint", "oxlint"] });oxlint is a binary behind a Node entry rather than a library, so this check runs
it and reads the JSON it answers with. Two things follow. Its severities are
oxlint's own — a rule set to "error" in .oxlintrc.json is reported as a
webpack error and one set to "warn" as a warning, and reportAs
moves them from there as it does for every check. And the options this check does
not name are not guessed at: write them as args, the flags oxlint
itself documents.
oxlintPath
- Type:
type oxlintPath = string;- Default:
oxlint
Path to the oxlint instance that will be used for linting.
configFile
- Type:
type configFile = string;- Default: unset, leaving oxlint to find its own
Path to the .oxlintrc.json to lint with.
args
- Type:
type args = string[];- Default:
[]
Arguments passed to oxlint as they are, for the flags this check does not name —
["--deny", "correctness"], say. Not --format: the check asks for JSON and
formats the results itself, and oxlint declines being asked twice.
Biome
Run with { use: "biome" }, and requires @biomejs/biome >= 2. It checks the
files webpack builds and reads whatever biome.json Biome finds for itself,
reporting at Biome's own severities.
new DiagnosticsPlugin({ checks: ["biome"] });Like the oxlint check this runs a binary and reads the JSON it answers with, which Biome calls an experimental reporter — a release of its own may move the shape, and it is the only reporter carrying the severities this check needs.
biomePath
- Type:
type biomePath = string;- Default:
@biomejs/biome
Path to the @biomejs/biome instance that will be used for checking.
command
- Type:
type command = "lint" | "check";- Default:
"lint"
Which of Biome's commands to run. "lint" runs the linter; "check" adds its
formatting and assist diagnostics, so a badly formatted file is reported as well
as one breaking a rule.
configFile
- Type:
type configFile = string;- Default: unset, leaving Biome to find its own
Path to the biome.json to check with.
args
- Type:
type args = string[];- Default:
[]
Arguments passed to Biome as they are, for the flags this check does not name.
Not --reporter: the check asks for JSON and formats the results itself.
TypeScript
Run with { use: "typescript" }, and requires typescript >= 5. It type checks
the program a tsconfig.json describes, so every file that config includes is
checked whether or not webpack built it, and a type error in a module nothing
imports yet is still reported. Emit is forced off: webpack writes the output.
new DiagnosticsPlugin({
checks: [{ use: "typescript", configFile: "tsconfig.build.json" }],
});Project references are read rather than built unless build says
otherwise, which is what a tsconfig.json listing only references needs.
Two things differ from the linters. A diagnostic belongs to the program rather
than to one file, so there is nothing to report a single file from. And
extensions only decides which files make the check run at all; what is checked
is whatever the config file includes.
A program is read whole, so threads buys it webpack's own thread
back rather than a share of the work: the check runs on one worker while webpack
builds, and is read where it is reported. Over a three-hundred-file solution
beside three hundred modules that took the build from 2149 ms to 1920 ms and the
module graph from 2004 ms to 385 ms, and an edit-to-report in watch from 259 ms
to 203 ms. A small project pays the thread's start without much to hide behind
it; threads: false keeps the work where webpack is.
While webpack watches, the program is kept and handed to the build after it, so a rebuild type checks what the change reaches rather than the project over again — about 30 ms rather than 300 over three hundred files, for the memory the program holds on to (some 50 MB there). A change to the config file, or to the compiler options given here, starts a new one.
Alongside the shared options you can pass any
compiler option — they override what
the config file sets, as --strict would on the command line.
typescriptPath
- Type:
type typescriptPath = string;- Default:
typescript
Path to the typescript instance that will be used for checking.
configFile
- Type:
type configFile = string;- Default: the nearest
tsconfig.jsonat or abovecontext
Path to the tsconfig.json that describes the program.
compilerOptions
- Type:
type compilerOptions = object;- Default: unset
Compiler options overriding the ones the config file sets. The same as writing them at the top level of the check, and useful when a name collides with one of the plugin's own.
build
- Type:
type build = boolean;- Default:
false
Build the projects the config file references before reporting, the way
tsc -b does. A tsconfig.json that only lists references describes an
empty program of its own, so without this a solution reports nothing at all;
with it, every project in the graph is checked in the order its references
say.
new DiagnosticsPlugin({
checks: [{ use: "typescript", build: true }],
});A reference is read through the declarations the referenced project publishes,
so building one is how the next is checked at all — the build therefore writes
each project's .d.ts and its .tsbuildinfo, and nothing else. The JavaScript
stays webpack's to write: a later tsc -b of your own still emits it.
mode says so in full, and can say otherwise.
The cost is a tsc -b pass rather than the kept program the check uses
otherwise. Over five projects and 205 files: 573 ms with nothing built yet,
4–8 ms when the solution is up to date, 258 ms after one file changed, and
around 200 ms on every rebuild for as long as an error stands, since a project
that fails is never up to date. Pair it with
reportAs: "log" if you would rather a watch rebuild did not wait
for that.
mode
- Type:
type mode = "readonly" | "write-tsbuildinfo" | "write-dts" | "write-references";- Default:
"write-dts"underbuild,"readonly"otherwise
What the check writes as well as reports. A check reports, so by default a program writes nothing at all and a solution writes only what the next project has to read.
| Mode | What reaches the disk |
|---|---|
readonly | Nothing. |
write-tsbuildinfo | Only .tsbuildinfo, which a later build resumes from. |
write-dts | .tsbuildinfo, .d.ts and .d.ts.map. |
write-references | Everything the compiler emits, the JavaScript included. |
new DiagnosticsPlugin({
checks: [{ use: "typescript", build: true, mode: "write-references" }],
});readonly under build is not the compiler's noEmit, which tsc -b refuses
with TS6310: the build runs and its output is kept where only that build can
read it, so a project still finds the declarations of the one it references
while your working tree is left alone.
diagnosticOptions
- Type:
interface diagnosticOptions {
syntactic?: boolean | undefined;
semantic?: boolean | undefined;
declaration?: boolean | undefined;
global?: boolean | undefined;
}- Default: every kind reported
Which kinds of diagnostic to report. Turning one off asks TypeScript for less
rather than dropping what it answered, so it is also the one filter that saves
the work — semantic: false is what makes a check that only wants syntax errors
cheap.
new DiagnosticsPlugin({
// webpack's own parser reports a syntax error in a file it builds
checks: [{ use: "typescript", diagnosticOptions: { syntactic: false } }],
});What the tsconfig.json itself is wrong about is reported whatever is turned
off here: with the config file misread, nothing below it would be answering the
right question. build reports the whole of what it finds either way,
since a solution is built rather than asked kind by kind.
fork-ts-checker-webpack-plugin spells this option the same way, with
semantic alone on by default.
Adding a check
A use may also be an adapter of its own rather than a built-in name, so a check can ship as its own package without an entry in this one:
new DiagnosticsPlugin({
checks: [
{ use: require("diagnostics-webpack-plugin-typescript"), strict: true },
],
});Such an adapter is an object with a name, and a create returning the five functions the plugin drives it through — what to lint, what came back, which results are errors and which warnings, how to format them, and what to release afterwards. It splits its results by their own severity and nothing else; reportAs is applied to what it returns:
module.exports = {
name: "made-up",
// "modules" lints the files webpack built, "glob" every file matching `files`
filesSource: "glob",
// Merged under the options the user passes, and under the shared options
defaults: { extensions: ["ts"] },
async create({ key, options, compilation }) {
return {
lintFiles: async (files) => runTheTool(files),
getResults: async (results) => results,
splitResults: (results) => ({ errors: results, warnings: [] }),
getFormatter: async (formatter) => async (results) => format(results),
cleanup: async () => {},
};
},
};label, filesSource, defaults, defaultExclude and schema are optional; the plugin fills in the defaults of a module-scanning check that excludes node_modules.
Migrating
Both plugins become one, and every option they had is still here. What changed is where an option is written and how the four that decided severity are spelled.
Where an option goes. context, lintOnStart and checks are the plugin's own and stay at the top level. Everything else is shared: write it at the top level to cover every check, or inside a checks entry to cover that one. configType, eslintPath and stylelintPath belong to a single check and go in its entry.
Severity is one option. emitError, emitWarning, failOnError, failOnWarning and quiet are reportAs, because reporting a result as a webpack error is what fails the build:
| Was | Is |
|---|---|
quiet: true, emitWarning: false | reportAs: { warnings: false } |
emitError: false | reportAs: { errors: false } |
emitError: false and emitWarning: false | reportAs: false |
failOnError: true | the default |
failOnError: false | reportAs: "warning" |
failOnWarning: true | reportAs: { warnings: "error" } |
The build is no longer aborted from inside the plugin. A result reported as a webpack error fails the build the way every other webpack error does — stats.hasErrors() is true and the CLI exits non-zero — and the assets are still written. Nothing about severity depends on mode any more.
Requirements. Node >= 22.12, webpack >= 5.106, and ESLint 9 or 10 / Stylelint 17 for whichever checks you run.
From eslint-webpack-plugin
-const ESLintPlugin = require("eslint-webpack-plugin");
+const DiagnosticsPlugin = require("diagnostics-webpack-plugin");
module.exports = {
plugins: [
- new ESLintPlugin({ extensions: ["js"], fix: true }),
+ new DiagnosticsPlugin({
+ checks: [{ use: "eslint", extensions: ["js"], fix: true }],
+ }),
],
};Every option eslint-webpack-plugin accepted, and where it is now:
| Option | Now |
|---|---|
cache | Unchanged, shared. |
cacheLocation | Unchanged, shared. The default moved to node_modules/.cache/diagnostics-webpack-plugin/.eslintcache. |
configType | Unchanged, in the eslint entry. |
context | Unchanged, top level. |
emitError | reportAs, see the table above. |
emitWarning | reportAs, see the table above. |
eslintPath | Unchanged, in the eslint entry. |
exclude | Unchanged, shared. |
extensions | Unchanged, shared. Still defaults to js. |
failOnError | reportAs. It defaulted to on outside development mode; the default no longer depends on mode. |
failOnWarning | reportAs, see the table above. |
files | Unchanged, shared. |
fix | Unchanged, shared. |
formatter | Unchanged, shared. |
lintDirtyModulesOnly | lintOnStart, inverted: lintDirtyModulesOnly: true is lintOnStart: false. Top level. |
outputReport | Unchanged, shared. It is still written even when reportAs is false. |
quiet | reportAs: { warnings: false }. |
resourceQueryExclude | Unchanged, shared. |
Any other option is passed to ESLint itself, as before.
From stylelint-webpack-plugin
-const StylelintPlugin = require("stylelint-webpack-plugin");
+const DiagnosticsPlugin = require("diagnostics-webpack-plugin");
module.exports = {
plugins: [
- new StylelintPlugin({ extensions: ["css"], threads: true }),
+ new DiagnosticsPlugin({
+ checks: [{ use: "stylelint", extensions: ["css"], threads: true }],
+ }),
],
};Every option stylelint-webpack-plugin accepted, and where it is now:
| Option | Now |
|---|---|
cache | Unchanged, shared. |
cacheLocation | Unchanged, shared. The default moved to node_modules/.cache/diagnostics-webpack-plugin/.stylelintcache. |
context | Unchanged, top level. |
emitError | reportAs, see the table above. |
emitWarning | reportAs, see the table above. |
exclude | Unchanged, shared. |
extensions | Unchanged, shared. Still defaults to css, scss and sass. |
failOnError | reportAs. It defaulted to on in every mode, and the default is still to fail on an error. |
failOnWarning | reportAs, see the table above. |
files | Unchanged, shared. |
formatter | Unchanged, shared. |
lintDirtyModulesOnly | lintOnStart, inverted: lintDirtyModulesOnly: true is lintOnStart: false. Top level. |
outputReport | Unchanged, shared. It is still written even when reportAs is false. |
quiet | reportAs: { warnings: false }. |
stylelintPath | Unchanged, in the stylelint entry. |
threads | Shared now, and "auto" by default rather than off. Every check honours it. |
Any other option is passed to Stylelint itself, as before. Two more things changed for Stylelint alone:
- Stylelint 17 or later is required.
stylelint-webpack-pluginaccepted13through17; the merged plugin drops the older majors rather than carrying their compatibility branches forward. Stylelint 17 itself needs Node>= 20.19. - Errors and warnings are no longer swapped.
failOnError: falseused to report errors as webpack warnings, andfailOnWarning: trueto report warnings as webpack errors. Each result now keeps its own severity unlessreportAssays otherwise — which is what those two spellings in the table above do, explicitly.
fix is a documented option now rather than one passed through to Stylelint unnamed. resourceQueryExclude is shared but has no effect here: it reads the query of a module webpack built, and Stylelint is given the files matching files instead.
Running both
The two plugins become one instance, and options they had in common are written once:
module.exports = {
plugins: [
- new ESLintPlugin({ context: "src", failOnError: true, extensions: ["js"] }),
- new StylelintPlugin({ context: "src", failOnError: true, extensions: ["css"] }),
+ new DiagnosticsPlugin({
+ context: "src",
+ checks: [
+ { use: "eslint", extensions: ["js"] },
+ { use: "stylelint", extensions: ["css"] },
+ ],
+ }),
],
};From fork-ts-checker-webpack-plugin
The typescript check does what that plugin does — type check the program a
tsconfig.json describes, rather than the files webpack happens to build — and
is written as one entry in checks:
-const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
+const DiagnosticsPlugin = require("diagnostics-webpack-plugin");
module.exports = {
plugins: [
- new ForkTsCheckerWebpackPlugin({
- typescript: { configFile: "tsconfig.build.json", build: true },
- }),
+ new DiagnosticsPlugin({
+ checks: [
+ { use: "typescript", configFile: "tsconfig.build.json", build: true },
+ ],
+ }),
],
};The check runs on a worker thread rather than in a process of its own. That
plugin forks one, which is why it has a memoryLimit and a profile; a thread
shares the build's heap and its copy of TypeScript, so neither is needed here.
The program is kept between rebuilds either way. What that costs and saves is
under the TypeScript check.
Over a generated 300-file project, three runs of each:
| Build | Edit to report, watching | Peak memory, process tree | |
|---|---|---|---|
fork-ts-checker-webpack-plugin@9.1.0 | 2232 ms | 138 ms | 429–488 MB |
| Here | 2227 ms | 131 ms | 269–301 MB |
The time is the same within the run-to-run spread; the memory is not, and that is the thread rather than anything clever.
It is a drop-in, and was checked as one against a project neither of us
wrote — the fork-ts-checker-webpack-plugin example that ts-loader ships,
which is React and TSX through ts-loader. Swapping the plugin for
{ use: "typescript" } and nothing else reported the same TS2322 at the same
line and column. Three things to expect while doing it:
- webpack must be 5.106 or newer, which is what the plugin requires at all.
npmrefuses to install alongside ESLint 8. Theeslintpeer here is optional but ranged^9 || ^10, and npm resolves an optional peer it can see, so a project pinned to 8 needs its ESLint updated — or--legacy-peer-deps— even when no ESLint check is configured.fork-ts-checker-notifier-webpack-pluginhas nothing to listen to. It taps that plugin's own hooks, so it goes when that plugin does.
fork-ts-checker-webpack-plugin | Here |
|---|---|
async | Not an option: the check runs on a worker thread while webpack builds (threads), and a check the compilation carries nothing of — reportAs "log" or false, with no outputReport — is run after a watch rebuild rather than during it. |
typescript.configFile | configFile, in the entry — see the TypeScript check. |
typescript.context | context, the plugin's own. |
typescript.build | build. |
typescript.configOverwrite | compilerOptions, or any compiler option written at the top of the entry. |
typescript.typescriptPath | typescriptPath. |
typescript.mode | mode. The same four values, and the same meaning; the default under build is write-dts rather than write-tsbuildinfo, because a referenced project is read through the declarations it publishes. |
typescript.memoryLimit, profile | Nothing to set — the check is not a forked process. |
typescript.diagnosticOptions | diagnosticOptions, spelled the same way. Every kind is reported here unless you turn one off; there, only semantic is on to begin with. |
issue.include, issue.exclude | ignoreDiagnostics, which takes the same match of a file, a code and a severity, or a function of your own. It says what to leave out, so an include is written as the exclude of everything else. |
formatter | formatter. Unset, TypeScript's own formatter is used, with color and the source line. |
logger | Webpack's logger is what the plugin writes to; reportAs: "log" is what sends results there rather than onto the compilation. |
devServer | Nothing to set: results reach the compilation, so a dev server overlays them, and reportAs: "log" keeps them off it. |
Running it next to a linter is one plugin rather than two, with one place to say how what they find is reported:
new DiagnosticsPlugin({
checks: [{ use: "eslint" }, { use: "typescript" }],
});Changelog
Contributing
We welcome all contributions!
If you're new here, please take a moment to review our contributing guidelines.
License
DllPlugin
DllPlugin과 DllReferencePlugin은 빌드 시간 성능을 크게 향상시키는 방식으로 번들을 분할하는 수단을 제공합니다. "DLL"이라는 용어는 원래 Microsoft에서 도입한 동적 링크 라이브러리를 나타냅니다.
DllPlugin
이 플러그인은 dll 전용 번들을 생성하기 위해 단독으로 별도의 webpack 설정에서 사용됩니다. 의존성을 매핑하기 위해 DllReferencePlugin에서 사용하는 manifest.json파일을 생성합니다.
context(선택): 매니페스트 파일의 요청 컨텍스트 (기본값은 webpack 컨텍스트)format(boolean = false):true인 경우 매니페스트 json 파일(출력)이 형식화됩니다.name: 노출된 dll 함수의 이름 (TemplatePaths:[fullhash],[chunkhash],[contenthash], &[name])path: 매니페스트 json 파일의 절대 경로 (출력)entryOnly(boolean = true):true인 경우 엔트리 포인트만 노출됩니다.type: dll 번들의 유형
new webpack.DllPlugin(options);주어진 경로에 기록되는 manifest.json을 생성합니다. 여기에는 require와 import 요청에서 모듈 id로의 매핑이 포함됩니다. 이는 DllReferencePlugin 에서 사용됩니다.
이 플러그인을 output.library옵션과 결합하여 dll 함수를 노출(전역 범위로 확대)합니다.
DllReferencePlugin
이 플러그인은 기본 webpack 설정에서 사용되며, 사전 빌드된 의존성을 require 하기 위해 dll 전용 번들을 참조합니다.
context: 매니페스트(또는 콘텐츠 프로퍼티)에 있는 요청의 (절대 경로) 컨텍스트extensions: dll 번들의 모듈을 해결하는데 사용되는 확장입니다(‘유효범위’를 사용할 때만 사용).manifest:content및name을 포함하는 객체 또는 컴파일 시 로드될 JSON 매니페스트의 절대 경로에 대한 문자열content(선택): 요청에서 모듈 id 로의 매핑(기본값은manifest.content)name(선택): dll이 노출되는 식별자(기본값은manifest.name)(externals참조)scope(선택): dll의 콘텐츠에 접근하는데 사용되는 접두사sourceType(선택): dll이 노출되는 방법(libraryTarget)
new webpack.DllReferencePlugin(options);dll 매니페스트 파일을 참조하여 의존성 이름을 모듈 id에 매핑한 다음, 내부 __webpack_require__함수를 사용하여 필요에 따라 요청합니다.
Modes
이 플러그인은 scoped 와 mapped 두 가지 모드에서 사용할 수 있습니다.
Scoped Mode
dll의 콘텐츠는 모듈 접두사 아래에서 접근 가능합니다. 즉, scope = 'xyz'로 dll의 파일 abc 는 require('xyz/abc')를 통해 접근할 수 있습니다.
Mapped Mode
dll의 콘텐츠는 현재 디렉터리에 매핑됩니다. 필요한 파일이 dll의 파일과 확인 후 일치하는 경우, dll의 파일이 대신 사용됩니다.
이 과정은 DLL 번들 내의 모든 파일을 해석한 후에 발생하므로, DLL 번들을 사용하는 쪽에서도 동일한 경로를 사용할 수 있어야 합니다. 예를 들어, DLL에 lodash와 abc 파일이 포함되어 있다면, 메인 번들에 해당 파일들을 포함시키는 대신 DLL 자체에서 import _ from "lodash"/require('lodash')와 import abs from "./abc"/require('./abc')를 사용하게 됩니다.
Usage
webpack.vendor.config.js
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
new webpack.DllPlugin({
context: import.meta.dirname,
name: "[name]_[fullhash]",
path: path.join(__dirname, "manifest.json"),
});webpack.app.config.js
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
new webpack.DllReferencePlugin({
context: __dirname,
manifest: path.join(__dirname, "manifest.json"),
scope: "xyz",
sourceType: "commonjs2",
});Examples
두 개의 개별 예제 폴더로, 유효범위와 컨텍스트를 보여줍니다.
References
Source
- DllPlugin source
- DllReferencePlugin source
- DllEntryPlugin source
- DllModuleFactory source
- ManifestPlugin source
Tests
EnvironmentPlugin
EnvironmentPlugin을 사용해 process.env 키에 DefinePlugin을 간단히 적용할 수 있습니다.
Usage
EnvironmentPlugin은 키로 구성된 배열 혹은 키에 기본값이 매핑된 객체를 받습니다.
new webpack.EnvironmentPlugin(["NODE_ENV", "DEBUG"]);이는 다음과 같은 DefinePlugin 적용과 동일합니다.
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
"process.env.DEBUG": JSON.stringify(process.env.DEBUG),
// since webpack 5.103.0
"import.meta.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
"import.meta.env.DEBUG": JSON.stringify(process.env.DEBUG),
});Usage with default values
또는, EnvironmentPlugin은 키에 기본값을 매핑한 객체를 지원합니다. 키가 process.env에 정의되지 않은 경우에는 기본값을 사용합니다.
new webpack.EnvironmentPlugin({
NODE_ENV: "development", // process.env.NODE_ENV가 정의되지 않은 경우 'development'를 사용하세요.
DEBUG: false,
});Example:
지금까지 구성해온 EnvironmentPlugin 설정을 테스트 파일 entry.js에 적용했을 때 어떻게 동작하는지 알아봅시다.
if (process.env.NODE_ENV === "production") {
console.log("Welcome to production");
}
if (process.env.DEBUG) {
console.log("Debugging output");
}빌드하기 위해 터미널에서 NODE_ENV=production webpack을 실행했을 때 entry.js는 다음과 같습니다.
if ("production" === "production") {
// <-- NODE_ENV의 'production'이 사용됩니다.
console.log("Welcome to production");
}
if (false) {
// <-- 기본값이 사용됩니다.
console.log("Debugging output");
}DEBUG=false webpack을 실행하면 다음과 같습니다.
if ("development" === "production") {
// <-- 기본값이 사용됩니다.
console.log("Welcome to production");
}
if ("false") {
// <-- DEBUG의 'false'가 사용됩니다.
console.log("Debugging output");
}Use Case: Git Version
다음과 같은 EnvironmentPlugin 설정은 저장소의 마지막 커밋에 따른 process.env.GIT_VERSION (예시 "v5.4.0-2-g25139f57f")과 process.env.GIT_AUTHOR_DATE (예시 "2020-11-04T12:25:16+01:00")를 제공합니다.
import childProcess from "node:child_process";
function git(command) {
return childProcess.execSync(`git ${command}`, { encoding: "utf8" }).trim();
}
new webpack.EnvironmentPlugin({
GIT_VERSION: git("describe --always"),
GIT_AUTHOR_DATE: git("log -1 --format=%aI"),
});DotenvPlugin
써드 파티 DotenvPlugin (dotenv-webpack)을 사용해 dotenv 변수를 사용할 수 있습니다.
// .env
DB_HOST=127.0.0.1
DB_PASS=foobar
S3_API=mysecretkeynew Dotenv({
path: "./.env", // env 파일 경로 (기본값)
safe: true, // .env.example 로드 (기본값은 dotenv-safe를 사용하지 않는 "false")
});EvalSourceMapDevToolPlugin
EvalSourceMapDevToolPlugin을 사용하면 소스맵 생성을 보다 세밀하게 제어할 수 있습니다. 또한 devtool 설정 옵션의 특정한 세팅에 의해 자동으로 활성화됩니다.
new webpack.EvalSourceMapDevToolPlugin(options);이 플러그인은 모든 모듈을 소스맵이 데이터 URL로 추가된 eval() 호출로 감싸며, .map 파일은 방출되지 않습니다. SourceMapDevToolPlugin과 동일한 옵션 객체를 허용하지만, 별도의 소스맵 파일을 설명하는 옵션인 filename, fileContext, publicPath는 여기서는 해당 없으므로 무시되며, 함수 값의 append도 마찬가지입니다.
Options
다음과 같은 옵션이 지원됩니다.
type Matcher =
| string
| RegExp
| ((name: string) => boolean)
| (string | RegExp | ((name: string) => boolean))[];string은 접두사로 일치하고, RegExp는 테스트로 일치하며, 함수는 이름과 함께 호출되어 일치 여부를 반환합니다.
test(Matcher): 경로가 주어진 값과 일치하는 모듈의 소스맵을 생성합니다. 기본값은 없습니다.test,include또는exclude로 제한하지 않으면 모든 모듈이 소스맵을 갖습니다. 모듈 자체 경로를 기준으로 하므로 청크나 번들 전체를 제외하는 데는 사용할 수 없습니다.include(Matcher): 주어진 값과 일치하는 모듈 경로에 대한 소스맵을 포함합니다.exclude(Matcher): 소스맵 생성에서 주어진 값과 일치하는 모듈을 제외합니다.append(string): 원래 애셋에 주어진 값을 추가합니다. 보통#sourceMappingURL주석입니다.[url]은 소스맵 파일의 URL로 대체됩니다.ignoreList(Matcher): 소스맵에서 지정된 값과 일치하는 소스 파일을 무시할지 여부를 결정합니다.module(boolean): 로더가 소스맵을 생성해야 하는지 여부를 나타냅니다(기본값은true).moduleFilenameTemplate(string):output.devtoolModuleFilenameTemplate을 참고하세요.columns(boolean): 열 매핑을 사용해야 하는지 여부를 나타냅니다(기본값은true).namespace(string): DevTools에서 여러 webpack 루트를 허용하는 네임스페이스 접두사입니다.output.devtoolNamespace를 참고하세요.noSources = false(boolean): 소스 파일 콘텐츠가 소스맵에 포함되지 않도록 합니다.sourceRoot(string): 소스맵의sourceRoot속성에 사용자 지정 값을 제공합니다.debugIds(boolean):true로 설정하면 소스 및 소스맵에 고유 ID가 생성되어 여러 빌드에서 소스맵을 식별하는 데 도움이 됩니다. 자세한 내용은 TC39 소스맵 디버그 ID 제안을 참고하세요.
Examples
다음 예시는 EvalSourceMapDevToolPlugin이 흔히 사용되는 사례를 보여줍니다.
Basic Use Case
다음 코드를 사용하여 설정 옵션 devtool: eval-source-map을 동등한 커스텀 플러그인 설정으로 바꿀 수 있습니다.
export default {
// ...
devtool: false,
plugins: [new webpack.EvalSourceMapDevToolPlugin({})],
};Exclude Vendor Maps
다음 코드는 node_modules에서 가져온 모든 모듈의 소스맵을 제외합니다.
new webpack.EvalSourceMapDevToolPlugin({
exclude: /node_modules/,
});HashedModuleIdsPlugin
이 플러그인은 해시가 모듈의 상대 경로를 기반으로 해 모듈 ID로 4개의 문자열이 생성됩니다.
new webpack.ids.HashedModuleIdsPlugin({
// 옵션...
});Options
이 플러그인은 다음 옵션을 지원합니다:
-
context: 이름을 만들기 위한 컨텍스트 디렉터리 (절대 경로). -
hashFunction: 사용할 해싱 알고리즘, 기본 값은'md4'입니다. Node.JS의crypto.createHash의 모든 기능이 지원됩니다. -
5.102.1+hashDigest: 해시를 생성할 때 사용할 인코딩, 기본 값은'base64'입니다. Node.JS의hash.digest의 모든 인코딩이 지원됩니다.표준 Node.js 인코딩 외에도, 이 플러그인은 URL 안전 해시 값 생성에 유용한
'base64url'도 지원합니다. 커스텀 진법('base26','base32','base36','base49','base52','base58','base62')은output.hashDigest에서만 사용 가능합니다. -
hashDigestLength: 사용할 해시 다이제스트의 접두사 길이, 기본 값은4입니다. 생성된 일부 ID는 모듈 ID 충돌을 피하기 위해 여기에서 지정된 것보다 길 수 있습니다.
Usage
다음은 이 플러그인을 사용하는 방법의 예시입니다:
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
new webpack.ids.HashedModuleIdsPlugin({
context: __dirname,
hashFunction: "sha256",
hashDigest: "hex",
hashDigestLength: 20,
});Using URL-safe encoding (5.102.1+):
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
new webpack.ids.HashedModuleIdsPlugin({
context: __dirname,
hashFunction: "md4",
hashDigest: "base64url", // URL-safe encoding
hashDigestLength: 8,
});HotModuleReplacementPlugin
HMR로 알려져 있는 Hot Module Replacement을 활성화합니다.
Basic Usage
HMR을 활성화하는 것은 간단하며, 이 플러그인은 옵션이 없습니다.
new webpack.HotModuleReplacementPlugin();IgnorePlugin
IgnorePlugin은 정규 표현식 또는 필터 함수와 일치하는 import 또는 require 호출에 대한 모듈 생성을 방지합니다.
Using regular expressions
resourceRegExp: 리소스 테스트를 위한 정규 표현식contextRegExp: (선택) 컨텍스트 (디렉터리) 테스트를 위한 정규 표현식
new webpack.IgnorePlugin({ resourceRegExp, contextRegExp });Using filter functions
checkResource (resource, context)는resource와context를 인수로 받는 필터 함수로, 불리언 값을 반환해야 합니다.
new webpack.IgnorePlugin({
checkResource(resource) {
// 리소스를 사용합니다
return true | false;
},
});Example of ignoring Moment Locales
moment 2.18 부터, 모든 로케일이 코어 라이브러리와 함께 번들로 제공됩니다 (깃허브 이슈를 참고하세요).
IgnorePlugin에 전달된 resourceRegExp 파라미터는 import 또는 require 되는 해석된 파일 이름 혹은 절대 모듈 이름에 대해 테스트 되지 않고, 가져오기가 실행되는 소스 코드 내에서 require 또는 import로 전달된 문자열 에 대해 테스트 됩니다. 예를 들어 node_modules/moment/locale/*.js를 제외하려는 경우 다음과 같은 작업이 수행되지 않습니다.
-new webpack.IgnorePlugin({ resourceRegExp: /moment\/locale\// });moment는 아래의 코드로 가져오기 때문입니다.
import(`./locale/${name}`);
// or
require(`./locale/${name}`);첫 번째 정규 표현식은 './locale/' 문자열과 일치해야 합니다. 그다음 두 번째 contextRegExp 파라미터를 사용하여 가져오기가 수행된 특정 디렉터리를 선택합니다. 다음은 로케일 파일이 무시되는 경우입니다.
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
});이는 'moment'로 끝나는 디렉터리에서 './locale'과 일치하는 모든 require 문이 무시됨을 의미합니다.
Internal webpack plugins
Webpack 내부적으로 사용하는 플러그인의 목록입니다.
내부 플러그인의 범주:
environment
컴파일러의 환경에 영향을 주는 플러그인입니다.
NodeEnvironmentPlugin
webpack.node.NodeEnvironmentPlugin()
컴파일러에 Node.js 스타일의 파일 시스템을 적용합니다.
compiler
컴파일러에 영향을 주는 플러그인입니다.
MemoryCachePlugin
MemoryCachePlugin()
모듈이 메모리에 캐시되는 컴파일러에 캐시를 추가합니다.
ProgressPlugin
ProgressPlugin(handler)
컴파일러에 훅을 하여 진행 정보를 추출합니다. handler는 (percentage: number, message: string) => void 서명이 있어야 합니다. Percentage는 0과 1 사이의 값으로 호출됩니다. 0은 시작, 1은 끝을 나타냅니다.
RecordIdsPlugin
RecordIdsPlugin()
레코드로부터 청크 아이디와 모듈을 저장하고 복원합니다.
entry
컴파일에 엔트리 청크를 포함하는 플러그인입니다.
EntryPlugin
EntryPlugin(context, entry, options)
컴파일에 엔트리 청크를 추가합니다. 청크의 이름은 options.name이고 오직 하나의 모듈만 포함합니다(의존성 추가). 모듈은 context (절대 경로)의 entry에서 확인됩니다.
PrefetchPlugin
PrefetchPlugin(context, request)
병렬 컴파일을 더 사용하기 위해서 의존성과 request를 프리페치합니다. 어떤 청크도 생성되지 않습니다. 모듈은 context (절대 경로)의 request에서 확인됩니다.
output
JsonpTemplatePlugin
web/JsonpTemplatePlugin()
청크는 JSONP-calls로 래핑되어 있습니다. 로딩 알고리즘은 엔트리 청크에 포함이 됩니다. <script> 태그를 추가함으로써 청크를 로드합니다.
옵션이 없으며 output.chunkLoading을 'jsonp'로 설정합니다. 출력 옵션을 통해 설정됩니다.
output.chunkLoadingGlobal이 JSONP 함수입니다.
output.publicPath는 청크를 로딩하기 위한 경로로 사용됩니다.
output.chunkFilename은 청크가 예상되는 파일 이름입니다.
NodeTemplatePlugin
node/NodeTemplatePlugin(options)
청크는 번들 모듈을 내보내는 Node.js 모듈로 래핑되어 있습니다. 엔트리 청크는 청크를 요구함으로써 로드됩니다.
options.asyncChunkLoading (boolean)은 output.chunkLoading으로 'require' 대신 'async-node'를 선택합니다.
LibraryTemplatePlugin
LibraryTemplatePlugin(name, target)
엔트리 청크들은 type 유형의 name 라이브러리로부터 꾸며집니다.
WebWorkerTemplatePlugin
webworker/WebWorkerTemplatePlugin()
청크는 importScripts에 의해 로드됩니다. 그렇지 않다면 JsonpTemplatePlugin과 비슷합니다.
옵션이 없으며 output.chunkLoading을 'import-scripts'로 설정합니다.
EvalDevToolModulePlugin
EvalDevToolModulePlugin(options)
//# sourceURL= 주석(options.sourceUrlComment, 기본값은 //# sourceURL=[url])이 달린 eval로 각 모듈을 감싸 모듈 템플릿을 꾸밉니다.
SourceMapDevToolPlugin
SourceMapDevToolPlugin(options)
각 청크에 대한 소스맵을 생성함으로써 템플릿들을 꾸밉니다.
options.filename은 소스맵의 파일 이름 템플릿입니다. [fullhash], [name], [id], [file] 및 [base]가 교체됩니다. 이 옵션이 없으면 소스맵은 DataUrl로 인라인 됩니다.
options.append, options.moduleFilenameTemplate, options.fallbackModuleFilenameTemplate 및 나머지 옵션은 SourceMapDevToolPlugin 페이지에서 설명합니다.
HotModuleReplacementPlugin
HotModuleReplacementPlugin()
Hot module replacement 대한 지원을 추가합니다. 런타임 코드를 추가한 템플릿을 꾸밉니다. `module.hot' API를 추가합니다.
옵션이 없으며 다음 출력 옵션을 읽습니다.
output.hotUpdateChunkFilename은 hot 업데이트 청크의 파일 이름입니다.
output.hotUpdateMainFilename은 hot 업데이트 매니페스트의 파일 이름입니다.
output.hotUpdateGlobal은 hot 업데이트를 로드하기 위한 전역 함수 이름입니다.
source
모듈의 소스 코드에 영향을 미치는 플러그인입니다.
APIPlugin
__webpack_* 변수(__webpack_public_path__, __webpack_require__, __webpack_modules__, __webpack_chunk_load__, __webpack_hash__, __webpack_nonce__, ...)를 모듈에서 접근 가능하게 하고, require.onError를 런타임 오류 핸들러(__webpack_require__.oe)에 매핑합니다.
CompatibilityPlugin
레거시 패턴을 재작성하여 다른 모듈 로더와의 호환성을 보장합니다. browserify 스타일의 require 위임자, 자체 __webpack_require__를 선언하는 사용자 코드, hashbang 라인 등이 해당됩니다.
ConstPlugin
파서에 의해 발사된 훅을 사용함으로써 dead branch를 추가로 제거를 가능하게 하려고 if (...)문과 삼항 연산자를 평가하여 true/false로 바꾸려고 시도합니다.
프로덕션 모드에는 dead branch와 관련하여 여러 최적화 들이 있습니다.
- Terser 이 수행한 최적화입니다.
- Webpack이 수행한 최적화 입니다.
Webpack은 조건문을 평가하려고 할 것입니다. 만약 성공한다면 dead branch가 제거됩니다. Webpck은 컴파일러가 알지 못하는 한 상수 폴딩을 할 수 없습니다. 다음 예시를 봅시다.
import { calculateTax } from "./tax";
const FOO = 1;
if (FOO === 0) {
// dead branch
calculateTax();
}위의 예시에서, webpack은 branch를 자를 수 없지만, Terser는 할 수 있습니다. 그러나 만약 FOO가 DefinePlugin 을 사용하면, webpack은 성공할 것입니다.
import { calculateTax } from './tax';도 제거된다는 점을 언급해야 합니다. 왜냐하면 calculateTax() 호출이 dead branch에 있어 제거되었기 때문입니다.
ProvidePlugin
ProvidePlugin(definitions)
definitions의 각 name: request 항목에서, name이 모듈에서 사용되면 import ... from <request>/import(<request>)/require(<request>)로 로드된 모듈로 채워집니다.
NodeStuffPlugin
NodeStuffPlugin(options)
Node.js 모듈에서는 일반적으로 사용할 수 있는 항목을 제공합니다.
또한, module을 사용하는 경우에는 일부의 Node.js 항목으로 채워지도록 합니다.
RequireJsStuffPlugin
require.js에서는 일반적으로 사용할 수 있는 항목을 제공합니다.
require[js].config는 제거됩니다. require.version은 0.0.0입니다. requirejs.onError는 require.onError에 매핑이 됩니다.
NodeSourcePlugin
node/NodeSourcePlugin()
이 플러그인은 하위 호환성을 위해 유지되는 no-op입니다. webpack 5는 더 이상 process, Buffer 등의 Node.js 코어 모듈이나 전역 변수를 폴리필하지 않습니다.
NodeTargetPlugin
node/NodeTargetPlugin()
만약 Node.js 환경에서 번들을 실행하는 경우에는 플러그인을 사용해야 합니다.
만약 네이티브 모듈이 번들 되어있더라도 올바르게 로드되도록 보장합니다.
AMDPlugin
dependencies/AMDPlugin(options)
AMD 스타일의 define과 require 모듈을 제공합니다. 또한, require.amd, define.amd 와 webpack_amd_options## 를 파라미터로 전달된 options에 바인딩합니다.
CommonJsPlugin
dependencies/CommonJsPlugin
CommonJs 스타일의 require 모듈을 제공합니다.
RequireContextPlugin
dependencies/RequireContextPlugin()
Provides require.context. Alternative requests for files are derived from the configured resolve options (modules, extensions, mainFiles, enforceExtension).
RequireEnsurePlugin
dependencies/RequireEnsurePlugin()
require.ensure를 제공합니다.
RequireIncludePlugin
dependencies/RequireIncludePlugin()
require.include를 제공합니다.
DefinePlugin
DefinePlugin(definitions)
식별자에 대해 상수를 정의합니다.
definitions은 객체입니다.
optimize
webpack.optimize 네임스페이스 아래의 모든 플러그인은 mode 가 'none'으로 설정된 경우에만 사용해야 합니다. 그렇지 않다면 플러그인은 두 번 적용되어야 합니다.
LimitChunkCountPlugin
optimize/LimitChunkCountPlugin(options)
병합 청크의 제한된 청크 수는 `options.maxChunks'보다 작습니다.
options.chunkOverhead and options.entryChunkMultiplicator are still accepted but no longer have any effect.
전체적인 크기를 가장 많이 줄인 청크가 첫 번째로 병합됩니다. 만약 여러 조합이 같으면 병합된 크기가 작은 것이 선택됩니다.
MergeDuplicateChunksPlugin
optimize/MergeDuplicateChunksPlugin()
같은 모듈의 청크가 병합됩니다.
RemoveEmptyChunksPlugin
optimize/RemoveEmptyChunksPlugin()
모듈을 포함하지 않는 청크(런타임 청크나 엔트리 청크가 아닌 것)는 제거됩니다.
MinChunkSizePlugin
optimize/MinChunkSizePlugin({ minChunkSize })
각 청크의 최소 크기가 minChunkSize가 될 때까지 청크를 병합합니다.
ModuleConcatenationPlugin
ModuleConcatenationPlugin page에 대한 구체적인 내용을 확인해보세요.
FlagIncludedChunksPlugin
optimize/FlagIncludedChunksPlugin()
청크에 포함된 청크의 청크 아이디를 추가합니다. 불필요한 청크 로드를 제거할 수 있습니다.
RealContentHashPlugin
optimize/RealContentHashPlugin()
optimization.realContentHash 옵션이 활성화 될 때, webpack은 내부적으로 RealContentHashPlugin을 컴파일러에 추가합니다.
Hook
RealContentHashPlugin은 해시 업데이트를 커스터마이징할 수 있는 훅인 updateHash 5.8.0+를 제공합니다.
import webpack from "webpack";
const { RealContentHashPlugin } = webpack.optimize;
// ...
compiler.hooks.compilation.tap("MyPlugin", (compilation) => {
const hooks = RealContentHashPlugin.getCompilationHooks(compilation);
hooks.updateHash.tap("MyPlugin", (content, oldHash) => {
// 이곳에서 원하는데로 해시를 계산할 수 있습니다.
});
});LimitChunkCountPlugin
코드를 작성할 때, 이미 필요한 것을 로드할 많은 코드 분할 지점을 추가했을 것입니다. 컴파일 후에 어떤 청크가 너무 작다는 것을 깨달을 것입니다. 이는 더 큰 HTTP 오버헤드를 만듭니다. LimitChunkCountPlugin은 청크를 합침으로써 그것들을 후속 처리할 수 있게 해줍니다.
new webpack.optimize.LimitChunkCountPlugin({
// Options...
});Options
다음의 옵션들이 지원됩니다.
maxChunks
number
청크의 최대 개수를 1보다 크거나 같도록 제한합니다. 1을 사용하는 것은 엔트리/메인 청크가 카운트에 포함될 때 추가적인 청크가 더해지는 것을 방지합니다.
webpack.config.js
import webpack from "webpack";
export default {
// ...
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 5,
}),
],
};minChunkSize
청크 크기를 지정한 제한 이상으로 유지하는 기능은 더 이상 이 플러그인의 기능이 아닙니다. 대신 MinChunkSizePlugin을 사용하세요.
ManifestPlugin
5.103.0+내장된 ManifestPlugin은 빌드에 대한 하나의 에셋 매니페스트를 생성합니다. WebpackManifestPlugin에서 포팅되었습니다.
webpack.config.js
new webpack.ManifestPlugin({
// options...
});Options
entrypoints
boolean = true
매니페스트에서 entrypoints 섹션 생성을 활성화합니다.
entrypoints가 활성화되어 있고 한 항목이 dependOn을 사용하는 경우
webpack.config.js
export default {
entry: {
entry1: {
import: "./src/file_1.js",
dependOn: "entry2",
},
entry2: "./src/file_2.js",
},
plugins: [
new webpack.ManifestPlugin({
filename: "manifest.json",
}),
],
};결과 매니페스트에는 부모/자식 관계 맵이 포함됩니다.
manifest.json
{
"entrypoints": {
"entry1": {
"imports": ["entry1.js"],
"parents": ["entry2"]
},
"entry2": {
"imports": ["entry2.js"]
}
}
}filename
string = manifest.json
디스크에 저장된 출력 파일의 파일 이름을 지정합니다. 기본적으로 플러그인은 output.path 디렉터리에 manifest.json 파일을 생성합니다.
filter
(item: ManifestItem) => boolean
매니페스트를 구성하는 파일을 필터링할 수 있습니다.
generate
(manifest: ManifestObject) => ManifestObject
매니페스트 객체를 수신하고 수정한 후 수정된 매니페스트를 반환합니다.
prefix
string = '[publicpath]'
매니페스트의 모든 키에 대한 경로 접두사를 지정합니다.
serialize
(manifest: ManifestObject) => string = (manifest) => JSON.stringify(manifest, null, 2)
매니페스트 객체를 수신하고 매니페스트 문자열을 반환합니다.
serialize를 사용하여 YAML과 같은 다른 형식을 출력합니다.
new webpack.ManifestPlugin({
serialize(manifest) {
return YAML.stringify(manifest, 4);
},
});Plugins
Webpack에는 많은 플러그인 인터페이스가 있습니다. Webpack 자체에 있는 대부분의 기능은 플러그인 인터페이스를 사용합니다. 이런점이 webpack을 유연하게 만듭니다.
아래 플러그인들은 기능별로 분류되어 있습니다. 대부분은 webpack에 포함되어 있어 new webpack.SomePlugin()으로 사용할 수 있으며, *-webpack-plugin 이름의 페이지에 있는 것들은 별도로 설치가 필요한 패키지입니다.
Sources and modules
| 이름 | 설명 |
|---|---|
DefinePlugin | 컴파일 시점에 설정되는 전역 상수를 사용할 수 있게 합니다 |
EnvironmentPlugin | process.env 키에 DefinePlugin을 사용하는 shorthand입니다 |
ProvidePlugin | import 또는 require 없이 모듈을 사용할 수 있게 합니다 |
IgnorePlugin | 번들에서 특정 모듈을 제외합니다 |
NormalModuleReplacementPlugin | 정규식과 일치하는 리소스를 교체합니다 |
ContextReplacementPlugin | require 표현식에서 추론된 컨텍스트를 재정의합니다 |
ContextExclusionPlugin | 일치하는 컨텍스트를 제외하여 webpack이 해당 모듈을 생성하지 않도록 합니다 |
VirtualUrlPlugin | 디스크에 파일 없이 빌드 시점에 콘텐츠가 생성되는 모듈을 만듭니다 |
Output and assets
| 이름 | 설명 |
|---|---|
BannerPlugin | 생성된 각 청크 상단에 banner를 추가합니다 |
CopyPlugin | 파일과 디렉터리를 출력 디렉터리로 복사합니다 |
ManifestPlugin | 소스 파일명을 방출된 출력 파일로 매핑하는 애셋 매니페스트를 생성합니다 |
ChunksWebpackPlugin | 번들 제공을 위해 엔트리 포인트와 청크 관계가 포함된 HTML 파일을 생성합니다 |
SvgChunkWebpackPlugin | 엔트리 포인트 디펜던시를 기반으로 SVGO가 최적화한 SVG 스프라이트를 생성합니다 |
Chunks and optimization
| 이름 | 설명 |
|---|---|
SplitChunksPlugin | 공유 코드와 vendor 코드를 별도 청크로 분리합니다 — optimization.splitChunks를 통해 설정합니다 |
ModuleConcatenationPlugin | 스코프 호이스팅 — production 모드에서 optimization.concatenateModules를 통해 기본적으로 활성화됩니다 |
MergeDuplicateChunksPlugin | 동일한 모듈을 포함하는 청크를 병합합니다 |
LimitChunkCountPlugin | 청크를 병합하여 최대 청크 수를 제한합니다 |
MinChunkSizePlugin | 청크 크기를 지정한 제한 이상으로 유지합니다 |
MinimizerPlugin | 프로젝트의 JS/CSS/HTML/JSON 등을 minify하고, 방출 결과를 압축합니다 |
HashedModuleIdsPlugin | 해시 기반 모듈 ID — production 기본값인 optimization.moduleIds: 'deterministic'을 대신 사용하세요 |
NoEmitOnErrorsPlugin | 컴파일 오류가 있을 때 emit 단계를 건너뜁니다 |
AutomaticPrefetchPlugin | 이전 컴파일의 모든 모듈을 미리 해결하여 워치 재빌드 속도를 높입니다 |
PrefetchPlugin | 첫 번째 import 전에 모듈을 미리 해결하고 빌드하여 크리티컬 빌드 경로를 단축합니다 |
DllPlugin | 번들을 사전 빌드된 vendor 번들로 분리합니다 — 파일시스템 캐시로 대부분 대체되었습니다 |
Module Federation
| 이름 | 설명 |
|---|---|
ModuleFederationPlugin | 런타임에 독립적으로 빌드된 애플리케이션 간에 모듈을 공유합니다 |
Development and diagnostics
| 이름 | 설명 |
|---|---|
HotModuleReplacementPlugin | Hot Module Replacement (HMR)를 활성화합니다 |
SourceMapDevToolPlugin | 소스맵을 더 세밀하게 제어할 수 있게 합니다 |
EvalSourceMapDevToolPlugin | eval 소스맵을 더 세밀하게 제어할 수 있게 합니다 |
ProgressPlugin | 컴파일 진행 상황을 보고합니다 |
ProfilingPlugin | 플러그인 실행 타이밍을 Chrome 프로파일 파일에 기록합니다 |
WatchIgnorePlugin | 워치 모드에서 일치하는 경로를 무시하여 재빌드가 발생하지 않게 합니다 |
DiagnosticsWebpackPlugin | 빌드 중 소스에 linter 및 기타 진단 도구를 실행합니다 |
더 많은 써드 파티 플러그인을 찾고 싶다면, awesome-webpack을 참고하세요.
MergeDuplicateChunksPlugin
동일한 모듈을 포함하는 청크를 병합합니다. 이 플러그인은 webpack의 내부 최적화의 일부로 기본적으로 활성화되어 있습니다. 이 플러그인을 공개적으로 내보내면 개발자는 병합이 발생하는 stage를 구성할 수 있습니다. 이는 청크 병합이 SplitChunksPlugin 이후에 실행되어야 할 때 유용합니다.
new webpack.optimize.MergeDuplicateChunksPlugin({
// 옵션...
});Options
stage
number
중복 청크 병합이 실행되는 최적화 단계를 제어합니다. 기본적으로 병합은 SplitChunksPlugin 이전에 발생합니다. stage를 10(SplitChunksPlugin의 단계) 보다 크게 설정하면 병합이 그 이후에 실행됩니다. 10 값은 사용자 플러그인이 먼저 적용되기 때문에 여전히 이전에 실행됩니다. 내장 인스턴스(단계 -10)는 활성 상태를 유지하므로, 이 설정은 두 번째 병합 패스를 추가합니다.
webpack.config.js
import webpack from "webpack";
export default {
// ...
plugins: [
new webpack.optimize.MergeDuplicateChunksPlugin({
stage: 11, // run after SplitChunksPlugin (stage 10)
}),
],
};MinChunkSizePlugin
minChunkSize보다 작은 청크를 병합하여 청크 크기를 설정한 한계 이상으로 유지합니다.
new webpack.optimize.MinChunkSizePlugin({
minChunkSize: 10000, // 최소 문자 수
});MinimizerWebpackPlugin
This plugin minifies your assets in a webpack build. It ships with several
built-in minimizers covering JavaScript, JSON, HTML, CSS, and images — pick one
with the minify option and target the right files with
test.
JavaScript minimizers:
terser—MinimizerPlugin.terserMinify(default). The same JavaScript-based minifier that webpack uses out of the box; produces small, well-tested output and supports the full set ofextractCommentsmodes.uglify-js—MinimizerPlugin.uglifyJsMinify. ES5-only minifier, useful when you specifically need UglifyJS-compatible output. Requiresnpm install --save-dev uglify-js.@swc/core—MinimizerPlugin.swcMinify. A very fast Rust-based JavaScript/TypeScript minifier. Requiresnpm install --save-dev @swc/core.esbuild—MinimizerPlugin.esbuildMinify. An extremely fast JS bundler/minifier; legal comments are always preserved (noextractCommentssupport). Requiresnpm install --save-dev esbuild.
JSON minimizer:
JSON.stringify—MinimizerPlugin.jsonMinify. Built in (no extra dependency); supportsspaceandreplaceroptions.
HTML minimizers:
- webpack's own —
webpack.html.syntax.htmlMinify. Ships with webpack (>= 5.111.0), so it needs no extra dependency, and it is the one that minifies what a document nests: an inline<style>, everystyle="", a<script>, an<svg>subtree. See webpack's own minimizers. html-minifier-terser—MinimizerPlugin.htmlMinifierTerser. JavaScript-based, no native dependency. Requiresnpm install --save-dev html-minifier-terser.@swc/html—MinimizerPlugin.swcMinifyHtml(full HTML documents) andMinimizerPlugin.swcMinifyHtmlFragment(HTML fragments, e.g.<template>content). Very fast Rust-based platform for the Web. Requiresnpm install --save-dev @swc/html.@minify-html/node—MinimizerPlugin.minifyHtmlNode. A Rust HTML minifier optimised for speed and effectiveness. Requiresnpm install --save-dev @minify-html/node.
CSS minimizers:
- webpack's own —
webpack.css.syntax.cssMinify. Ships with webpack (>= 5.111.0), so it needs no extra dependency, and it minifies the CSS a stylesheet or a document nests. See webpack's own minimizers. cssnano—MinimizerPlugin.cssnanoMinify. Built on top of PostCSS. Requiresnpm install --save-dev cssnano postcss.csso—MinimizerPlugin.cssoMinify. A CSS minifier with structural optimisations. Requiresnpm install --save-dev csso.clean-css—MinimizerPlugin.cleanCssMinify. A widely-used CSS optimiser. Requiresnpm install --save-dev clean-css.esbuild—MinimizerPlugin.esbuildMinifyCss. Very fast CSS minification using esbuild's CSS loader. Requiresnpm install --save-dev esbuild.lightningcss—MinimizerPlugin.lightningCssMinify. A Rust-based CSS parser, transformer, and minifier. Requiresnpm install --save-dev lightningcss.@swc/css—MinimizerPlugin.swcMinifyCss. A very fast Rust-based CSS minifier. Requiresnpm install --save-dev @swc/css.
Image minimizers:
sharp—MinimizerPlugin.sharpMinify. Re-encodes an image as the format its name already claims (avif,gif,heif,jp2,jpeg,png,tiff,webp), and can resize, rotate, flip, grayscale, blur or sharpen on the way — from the options or from the asset's own name. Requiresnpm install --save-dev sharp.svgo—MinimizerPlugin.svgoMinify. Minifies SVG, including anasset/inlineSVG that notestcan match. Requiresnpm install --save-dev svgo.imagemin—MinimizerPlugin.imageminMinify. Runs theimageminplugins you name. Requiresnpm install --save-dev imageminplus each plugin. Pair it withMinimizerPlugin.imageminGenerateundergeneratewhen a plugin converts the format, since only that can rename the asset.@napi-rs/image—MinimizerPlugin.napiRsImageMinify. Rust codecs with no system dependency; recompressespnglosslessly with oxipng andjpegwith mozjpeg, and can resize, turn, mirror, grayscale, invert or blur on the way — from the options or from the asset's own name. Requiresnpm install --save-dev @napi-rs/image.
These only minify — they never change an image's format or name; see Images.
Transport encodings:
zliband anything shaped like it —MinimizerPlugin.compress. Compresses an asset so a server can serve it underContent-Encoding. Takesalgorithm— azlibfunction's name (gzip,brotliCompress,deflate,zstdCompress, …) or one of your own — andcompressionOptionsfor it, and needs no extra dependency. Give it tominifyto compress an asset in place, or togenerateas anassetgenerator to write the compressed file beside the original; either way it puts itself after the minimizers, through agetStageof its own.
All of the non-default minimizers are declared as optional peer
dependencies — install only the ones you actually use. One plugin instance
covers several languages at once: give minify an array and each
minimizer is offered only the assets its own filter accepts, so JS, CSS,
HTML and JSON share a single worker pool (see Examples).
Getting Started
Webpack v5 comes with the latest minimizer-webpack-plugin out of the box.
If you are using Webpack v5 or above and wish to customize the options, you will still need to install minimizer-webpack-plugin.
Using Webpack v4, you have to install terser-webpack-plugin v4 (minimizer-webpack-plugin is only published for Webpack v5+).
To begin, you'll need to install minimizer-webpack-plugin:
npm install minimizer-webpack-plugin --save-dev
or
yarn add -D minimizer-webpack-plugin
or
pnpm add -D minimizer-webpack-plugin
Then add the plugin to your webpack configuration. For example:
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [new MinimizerPlugin()],
},
};Finally, run webpack using the method you normally use (e.g., via CLI or an npm script).
Note about source maps
Works only with source-map, inline-source-map, hidden-source-map and nosources-source-map values for the devtool option.
Why?
evalwraps modules ineval("string")and the minimizer does not handle strings.cheaphas no column information and the minimizer generates only a single line, which leaves only a single mapping.
Using supported devtool values enable source map generation.
Options
testincludeexcludeparallelminifyminimizerOptions(deprecated)generateextractComments
test
Type:
type test = string | RegExp | (string | RegExp)[];Default: /\.m?js(\?.*)?$/i
Test to match files against.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
test: /\.js(\?.*)?$/i,
}),
],
},
};include
Type:
type include = string | RegExp | (string | RegExp)[];Default: undefined
Files to include.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
include: /\/includes/,
}),
],
},
};exclude
Type:
type exclude = string | RegExp | (string | RegExp)[];Default: undefined
Files to exclude.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
exclude: /\/excludes/,
}),
],
},
};parallel
Type:
type parallel = boolean | number;Default: true
Use multi-process parallel running to improve the build speed.
Default number of concurrent runs: os.cpus().length - 1 or os.availableParallelism() - 1 (if this function is supported).
Note
Parallelization can speedup your build significantly and is therefore highly recommended.
Warning
If you use Circle CI or any other environment that doesn't provide the real available count of CPUs then you need to explicitly set up the number of CPUs to avoid
Error: Call retries were exceeded(see #143, #202).
boolean
Enable/disable multi-process parallel running.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
parallel: true,
}),
],
},
};number
Enable multi-process parallel running and set number of concurrent runs.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
parallel: 4,
}),
],
},
};minify
Type:
type minifyFn = (
input: Record<string, string | Buffer>,
sourceMap: import("@jridgewell/trace-mapping").SourceMapInput | undefined,
minifyOptions: {
module?: boolean | undefined;
ecma?: import("terser").ECMA | undefined;
},
extractComments:
| boolean
| "all"
| "some"
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
},
) => boolean)
| {
condition?:
| boolean
| "all"
| "some"
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
},
) => boolean)
| undefined;
filename?: string | ((fileData: any) => string) | undefined;
banner?:
string | boolean | ((commentsFile: string) => string) | undefined;
}
| undefined,
) => Promise<{
code: string;
map?: import("@jridgewell/trace-mapping").SourceMapInput | undefined;
errors?: (string | Error)[] | undefined;
warnings?: (string | Error)[] | undefined;
extractedComments?: string[] | undefined;
}>;
interface minimizer {
implementation: minifyFn;
options?: Record<string, any>;
}
type minify = minifyFn | (minifyFn | minimizer)[] | minimizer;
// An empty array is nothing to minify.Default: MinimizerPlugin.terserMinify
Which minimizer runs, and the options it runs with. By default the plugin uses
terser; overriding it is also how you test
an unpublished version or a fork. The default stands whether or not a
generate was configured too, and so does test's: the
JavaScript minifier and the names it reads are what this plugin is.
Nothing minifying is an empty array rather than a missing value, for an
instance whose whole job is its generate:
new MinimizerPlugin({
test: /.*/,
minify: [],
generate: {
implementation: MinimizerPlugin.compress,
options: { algorithm: "gzip" },
type: "asset",
filename: "[path][base].gz",
},
});false is not accepted. A list with nothing in it needs no guard at any of the
places that run the minimizers — each simply does nothing — while a second kind
of value does.
Warning
Always use
requireinsideminifyfunction whenparalleloption enabled.
object
The form to prefer: the minimizer and its own options in one place.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.swcMinify,
options: { mangle: false },
},
}),
],
},
};options reaches the minify function as its third argument, and what it takes
is that minimizer's own: Terser's
minify options for the
default one, and the tables in
webpack's own minimizers for cssMinify and
htmlMinify.
implementation may also name a module path rather than hold the function
itself — a string, or { path, export } for a named export, the way
sass-loader takes its implementation. A worker then requires the
minimizer, which is what the default terser is named by; one written as a
function is handed over as source for the worker to rebuild with new Function:
new MinimizerPlugin({
minify: {
// Or the string on its own, where the module exports the function itself.
implementation: {
path: require.resolve("./my-minifier"),
export: "minify",
},
},
});The saving is per task, so it needs every minimizer of that task to be nameable:
one written as a function anywhere in the list leaves the whole task on the
source path, since that function has no other way across. So does a function
anywhere in what the task carries — an extractComments callback, or a
minimizer's own options holding one — because a required minimizer is handed
the payload as it is, and a structured clone throws on a function.
filter(name, info) states which assets this minimizer is offered — return
false to decline one, and anything else (undefined included) to accept. It
answers for a filter property on the minimizer function itself, which is what
the built-ins carry, so setting it here is how you narrow one of them without
wrapping it.
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.sharpMinify,
options: { encodeOptions: { jpeg: { quality: 80 } } },
filter: (name) => !name.includes("do-not-touch"),
},
});Two keys are filled in before a minimizer sees them, and only when options
does not set them itself: ecma, from
output.environment,
and module, from the asset's own javascriptModule info or its .mjs /
.cjs extension. Setting either yourself wins, including module: false.
The default minimizer's options, written out
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.terserMinify,
options: {
ecma: undefined,
parse: {},
compress: {},
mangle: true, // Note `mangle.properties` is `false` by default.
module: false,
// Deprecated
output: null,
format: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_classnames: undefined,
keep_fnames: false,
safari10: false,
},
},
}),
],
},
};array
Several minimizers are an array of objects, rather than one object holding two
lists that have to line up. Each asset is dispatched to the minimizers whose
filter accepts it, and when more than one accepts the same asset the output
of each is fed to the next (the chain semantic). Warnings, errors and extracted
comments from all of them are merged.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: [
{
implementation: MinimizerPlugin.terserMinify,
options: { mangle: false },
},
{ implementation: MinimizerPlugin.swcMinify },
],
}),
],
},
};Each entry carries its own filter as well as its own options, which is how
the same minimizer runs twice over different assets:
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
minify: [
{
implementation: MinimizerPlugin.sharpMinify,
options: { encodeOptions: { jpeg: { quality: 60 } } },
filter: (name) => name.includes("thumb"),
},
{
implementation: MinimizerPlugin.sharpMinify,
options: { encodeOptions: { jpeg: { quality: 90 } } },
filter: (name) => !name.includes("thumb"),
},
],
});This is also what lets one plugin instance and one worker pool handle every
asset type: each built-in ships with a filter matching its natural
extension, so JS, CSS, HTML and JSON need no second instance. test still
defaults to JS only, so widen it to let the other assets reach the dispatcher:
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
test: /\.(?:[cm]?js|css|html?|json)(\?.*)?$/i,
minify: [
{ implementation: MinimizerPlugin.terserMinify },
{ implementation: MinimizerPlugin.cssnanoMinify },
{ implementation: MinimizerPlugin.htmlMinifierTerser },
{ implementation: MinimizerPlugin.jsonMinify },
],
}),
],
},
};Pair it with webpack's own cssMinify and
htmlMinify and the source a document or a stylesheet nests inside itself is
minified too — see Embedded source.
function
A bare function is shorthand for an object with no options. A custom one
says what it can do through properties on the function itself — each optional,
and each answering a question the plugin would otherwise have to guess at.
webpack.config.js
// Can be async
const minify = (input, sourceMap, minimizerOptions, extractsComments) => {
// Whatever the `minify` option's `options` holds reaches the third argument
// You can use `minimizerOptions.myCustomOption`
// Custom logic for extract comments
const { map, code } = require("uglify-module") // Or require('./path/to/uglify-module')
.minify(input, {/* Your options for minification */});
return { map, code, warnings: [], errors: [], extractedComments: [] };
};
// Used to regenerate `fullhash`/`chunkhash` between different implementation
// Example: you fix a bug in custom minimizer/custom function, but unfortunately webpack doesn't know about it, so you will get the same fullhash/chunkhash
// to avoid this you can provide version of your custom minimizer
// You don't need if you use only `contenthash`
minify.getMinimizerVersion = () => {
let packageJson;
try {
packageJson = require("uglify-module/package.json");
} catch (error) {
// Ignore
}
return packageJson && packageJson.version;
};
// Restrict the minimizer to the assets it can actually handle. The plugin
// skips assets for which `filter` returns `false` and (when an array of
// minimizers is used) dispatches each asset only to the minimizers that
// accept it. Returning `undefined` is treated as accept.
minify.filter = (name) => /\.[cm]?js(\?.*)?$/i.test(name);
// The languages this minimizer minifies. Source a module embeds in another
// language carries no filename, so `filter` cannot dispatch it and this does
// — a function without it is never handed any.
minify.getTypes = () => ["javascript"];
// Declare this when the minimizer reads the asset's bytes rather than its
// text — an image minimizer. Its `input` values then arrive as a `Buffer` and
// its `code` may be one. Only applied when every minimizer an asset is
// dispatched to declares it, since one that does not could not read the bytes.
minify.supportsBinary = () => true;
// Declare this when the minimizer has to run somewhere other than where
// minification does — it is handed the `Compilation` class so it can name a
// stage rather than a number. Each minimizer runs where it asks, chaining
// through the asset a later one reads back; one asking for nothing runs where
// minifying belongs.
minify.getStage = (compilation) =>
compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER;
// The name this function's work goes under: the asset it writes is marked
// with it in the asset info, and stats print it. A minimizer declaring
// nothing minified the asset, so `minimized`; a generator declaring nothing
// wrote a new file, so `generated`. It is also what is not run twice — an
// asset already marked with every name a function writes is declined, which
// is how one minified by a child compilation is left alone.
minify.getAssetFlag = () => "compressed";
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: minify,
options: { myCustomOption: true },
},
}),
],
},
};A minifier that nests other languages inside what it prints — a document
with an inline <style>, a stylesheet with a data: URL — hands each nested
body out instead of minifying it itself. It declares which languages it can
offer, and the plugin passes renderEmbeddedSource in its options when
something else claims one of them:
async function myHtmlMinify(input, sourceMap, minimizerOptions) {
const [[, code]] = Object.entries(input);
const { renderEmbeddedSource } = minimizerOptions;
// Absent when nothing configured claims a language this minifier offers
const minifyStyle = async (css) => {
const result = renderEmbeddedSource
? await renderEmbeddedSource(css, { type: "css" })
: undefined;
// Declining leaves the body exactly as it was written
return result && typeof result.code === "string" ? result.code : css;
};
return { code: await print(code, minifyStyle) };
}
// The languages this minifier can hand out, given the options it will run with
myHtmlMinify.getEmbeddedTypes = (minimizerOptions) => ["css", "javascript"];renderEmbeddedSource(source, { type, as }) answers
{ code?, warnings?, errors? } or undefined, and recurses — a body that
nests something of its own is reached too. as says which production of
type the body is; a style="" is "block-contents" rather than a whole
stylesheet. Embedded source has the whole picture.
minimizerOptions
Note
Deprecated, and to be removed in the next major release. Give a minimizer its options in
minifyinstead, which keeps one minimizer's configuration in one place. It keeps working, and setting the options for one minimizer in both places is an error.
Type:
type minimizerOptions = Record<string, any> | Record<string, any>[];Default: Terser's
Options for the active minimizer, given away from it. With an array of minimizers it may be an array too — each element goes to the minimizer at the same index — or one object every minimizer is handed. It is still the only way to configure the default minimizer without naming it:
module.exports = {
optimization: {
minimize: true,
minimizer: [new MinimizerPlugin({ minimizerOptions: { mangle: false } })],
},
};terserOptions is a deprecated alias of it — passing either is equivalent, and
minimizerOptions wins if both are set.
generate
Type:
type generateFn = (
input: Record<string, string | Buffer>,
sourceMap: undefined,
generatorOptions: Record<string, any>,
) => Promise<{
code: string | Buffer;
filename?: string;
errors?: (Error | string)[];
warnings?: (Error | string)[];
}>;
interface generator {
implementation: generateFn;
options?: Record<string, any>;
type?: "import" | "asset";
filename?: string | ((pathData: any) => string);
filter?: (name: string) => boolean;
deleteOriginalAssets?: boolean | ((name: string) => boolean);
threshold?: number;
minRatio?: number;
relatedName?: string | false;
}
type generate =
| generateFn
| generateFn[]
| generator
| Record<string, generateFn | generateFn[] | generator>;Default: undefined
Rewrites a module's own bytes as it is built, rather than an asset after
the build. That is the one point at which a re-encoding can also rename —
return a filename and the asset is emitted under it, with every reference in
the bundle following. minify cannot: an asset's name is decided
during code generation, so renaming there would leave the bundle pointing at a
URL nothing emits.
test, include and exclude select which
modules it sees, matched against the module's resource — query and all, so
?as=webp is matchable. A generator runs in the webpack process, since
modules build before the worker pool is up, and its answer is cached under the
bytes plus the generator and its options.
Two generators ship with the plugin, and they differ in who picks the format.
sharpGenerate is told: it takes the target from the request's ?as= or,
failing that, from an options.encodeOptions naming exactly one format, and reports an error when neither says which format
to write. imageminGenerate is not: its plugins decide, so it reads the format
back off the bytes they produced and renames to match, leaving an asset its
plugins did not convert under the name it had.
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png)$/i,
type: "asset/resource",
// The query has to survive into the name, or two formats of one image
// collide.
generator: { filename: "[name][ext][query]" },
},
],
},
plugins: [
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
generate: MinimizerPlugin.sharpGenerate,
}),
],
};// `image.jpg` is emitted as `image.webp`, and this import points at it.
import webp from "./image.jpg?as=webp";Written as an object, generate names its generators, and an asset picks
one by name with ?as=:
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
generate: {
webp: {
implementation: MinimizerPlugin.sharpGenerate,
options: { encodeOptions: { webp: { quality: 90 } } },
},
avif: {
implementation: MinimizerPlugin.sharpGenerate,
options: { encodeOptions: { avif: { quality: 50 } } },
},
},
});// And `./image.jpg?as=avif` for the other one.
import webp from "./image.jpg?as=webp";A module naming no preset is left alone, so the same build can import an image unconverted. One naming a preset nothing defines is an error rather than a silent decline, since the name it asked for is what the bundle would point at.
A generator can also be written as an object — on its own, or under a name — which is where its own options live and what lets it read what was emitted instead of a module as it builds:
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
generate: {
webp: {
implementation: MinimizerPlugin.sharpGenerate,
options: { encodeOptions: { webp: {} } },
type: "asset",
// Optional, a template or a function answering with one. Without it the
// generator's own name for the result is used, which for `sharpGenerate`
// is the original with its extension replaced.
filename: "[path][name].webp",
// Optional. Narrows what this generator reads, on top of `test`.
filter: (name) => !name.includes("icons/"),
// Optional, `false` by default: the asset it read stays where it is.
// Written as a function it is asked per asset.
deleteOriginalAssets: false,
// Optional. Skips an asset this small, before the generator is asked.
threshold: 10240,
// Optional. Drops the result unless it is this much smaller than what
// it read, as `generated size / original size`.
minRatio: 0.8,
// Optional. The key the new file is recorded under in the original's
// `related` info, which is how a server asked for one finds the other.
relatedName: "webp",
},
},
});type decides which of the two things a generator does, and they are not
interchangeable — they read different input, at different points in the build:
"import" (the default) | "asset" | |
|---|---|---|
| Reads | a module, as it builds | an asset, once it is emitted |
| Produces | that module's own bytes, renamed with them | a new file beside the one it read |
| Picked by | ?as=<name> on the import | test / include / exclude, then filter |
| Reaches a file nothing imports | no | yes — copied assets included |
| Fields it reads | implementation, options | those plus filename, filter, deleteOriginalAssets, threshold, minRatio, relatedName |
| webpack | 5.111 or newer | any supported version |
"import" is the only point at which a rename can reach the bundle: the
asset is named while its module is built, so every reference follows it. The
import that asked for the conversion gets the converted file.
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
generate: { webp: { implementation: MinimizerPlugin.sharpGenerate } },
});import url from "./photo.jpg?as=webp"; // url is "photo.webp"
const same = new URL("./photo.jpg?as=webp", import.meta.url); // photo.webp.hero {
background: url("./photo.jpg?as=webp"); /* photo.webp */
}photo.webp the jpg became this
Those are one asset module between them, so an import, a new URL() and a
CSS url() all follow the rename. An asset inlined as a data URI carries no
file name, and takes the media type of what it became — data:image/webp;….
"asset" leaves what it read alone and writes another file next to it, so
both survive — the <picture> case, where the .webp goes in a srcset you
write yourself and the .jpg stays as the fallback. Nothing imports the new
file, so ?as= cannot reach it and its preset name selects nothing; the name
is only how you address its options. An asset already generated is never
generated from again.
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
generate: {
webp: { implementation: MinimizerPlugin.sharpGenerate, type: "asset" },
},
});import url from "./photo.jpg"; // url is "photo.jpg", unchangedphoto.jpg still there, unless `deleteOriginalAssets`
photo.webp generated beside it
filename, filter, deleteOriginalAssets, threshold, minRatio and
relatedName describe a file being written beside another, so they belong to
"asset" and setting one on an "import" generator is an error rather than a
field that quietly does nothing.
Three of them decide whether the new file is worth having. threshold skips an
asset too small to bother with, before the generator is asked at all. minRatio
drops a result that is not enough smaller than what it read, since a file that
saves nothing still costs a request. relatedName records the new file under
that key in the original's related info — which is how a server asked for the
original finds it — and declines an asset already carrying that key.
A file written under the original's own name has replaced it, so there is
nothing beside it to delete and nothing for relatedName to point at; a
generator doing that re-encodes an asset in place.
Deleting takes the original file and nothing else. webpack deletes whatever an
asset's related names along with it, so a source map, or the file a second
generator wrote beside the same original, would go too; the original goes
alone instead. Where the generated file took the original's own name there is
nothing left to delete, and relatedName is not recorded when the asset that
would carry it is being deleted.
A generated file inherits nothing from the one it was read from: what the
original's info says about its hashes, its module and where its source came
from is true of that file and not of this one. The exception is immutable,
and only where filename still derives from the original's name — [name],
[base] or [file] — since that is what carried the hash the promise rests
on.
When a generator runs is not among them, because it is not the config's to
say: the implementation declares it through a getStage of its own, the way it
declares everything else about itself — see minify. Compressing has
to read the bytes a user downloads, so MinimizerPlugin.compress asks for
PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER and runs after every minimizer has had
its say; one that asks for nothing runs where minifying does. The name its
work goes under is the implementation's too, through the same kind of helper:
a generator declaring nothing wrote a new file, so generated, while compress
says compressed, another encoding of the bytes being no smaller a version of
them. Whatever the name, stats print it and the generator declines a file
already carrying it, so nothing reads its own output back.
MinimizerPlugin.compress ships with the plugin and is written against that.
algorithm says which compression to run — a zlib function's name, or one of
your own taking (input, options, callback) — and compressionOptions is what
that algorithm is run with, the way terserMinify takes terser's own options.
Here it is as an asset generator, which writes the compressed file beside the
one it read, so both survive and the URL says which is which:
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
test: /\.(js|css|html|svg)$/i,
generate: {
gzip: {
implementation: MinimizerPlugin.compress,
options: { algorithm: "gzip" },
type: "asset",
filename: "[path][base].gz",
},
brotli: {
implementation: MinimizerPlugin.compress,
options: {
algorithm: "brotliCompress",
compressionOptions: { params: {} },
},
type: "asset",
filename: "[path][base].br",
},
},
}),
],
},
};Minifying and compressing are then one plugin over one pass of filtering and one
cache, and the ordering they need — compress what minification produced — is
what stage states rather than what applying two plugins in the right order
happens to give. Each algorithm is run at its own maximum by default (zlib's
best level, brotli's best quality); name compressionOptions to say otherwise.
Compressing with minifying, and without
The example above does both: minify defaults to terser, so the bundle is
minified and the compressed files are written from what minification produced.
That is the shape to want — one pass of filtering, one cache, and stage
ordering the two — and it needs nothing said about minify at all:
new MinimizerPlugin({
test: /\.(js|css|html|svg)$/i,
generate: {
implementation: MinimizerPlugin.compress,
options: { algorithm: "gzip" },
type: "asset",
filename: "[path][base].gz",
},
});Compressing only — an instance that must not touch what it reads — says so
with an empty list of minimizers. test is then yours to state too, since the
.js default belongs to minifying:
new MinimizerPlugin({
test: /.*/,
minify: [],
generate: {
implementation: MinimizerPlugin.compress,
options: { algorithm: "gzip" },
type: "asset",
filename: "[path][base].gz",
},
});Nothing is minified, no asset is marked minimized, and the bundle keeps the
name it would have had without this plugin — an instance that rewrites nothing
salts no hash.
It is an ordinary minimizer too, so minify takes it the way it
takes terserMinify or swcMinify. There it compresses the asset in place
rather than beside it — the shape for a server that says what the encoding is
through Content-Encoding while the URL stays as it was:
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
test: /\.js$/i,
minify: MinimizerPlugin.compress,
minimizerOptions: { algorithm: "gzip" },
}),
],
},
};An array runs its minimizers in order, each one reading what the last produced,
so minifying and then compressing in place is one entry after another — and each
states its own options. Each runs at the stage it asks for — terser before the
hash is taken, compress after — so [contenthash] still names what
minification produced:
new MinimizerPlugin({
test: /\.js$/i,
minify: [
{ implementation: MinimizerPlugin.terserMinify },
{
implementation: MinimizerPlugin.compress,
options: { algorithm: "brotliCompress" },
},
],
});ecma is filled in from
output.environment
unless a generator's options set it, the same way it is for a minimizer's —
see minify.
filename is a
webpack filename template
resolved against the asset read, so [path], [name], [base], [ext] and
[query] are available; content hashes are not, because the name derives from
one the original already carries.
In watch mode the rename is carried on the module rather than reapplied each build, so a rebuild that does not touch the image keeps pointing at the generated name without running the generator again. Changing the image does run it again, since its answer is cached under the bytes.
The same holds across runs under
cache.type: "filesystem",
where a module is restored from the pack rather than rebuilt. That restored
result is the generator's, so changing a generator or its options has to
invalidate the pack, and the plugin adds their identity to
cache.version so
it does. This needs the plugin to be in the config — plugins or
optimization.minimizer — since webpack builds the cache while it applies
them; a plugin applied by hand after webpack() returns is too late to reach
it, and a changed generator would then be ignored until the cache directory is
removed.
Note
Note
generatorOptionsis kept as a deprecated way of giving a generator its options — one object for one generator, an array positionally matching an array of them, or keyed by name wheregeneratenames its generators. Prefer a generator's ownoptions. Setting both for one generator is an error, and a key naming no generator is an error rather than silently doing nothing.
Note
An
"import"generator needs a webpack whoseNormalModuleprocessResulthook can be awaited (5.111 or newer), since that is where a rename has to happen. On an older webpack the plugin reports an error rather than silently generating nothing. An"asset"generator does not use that hook and works on any supported webpack.
extractComments
Type:
type extractComments =
| boolean
| string
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
},
) => boolean)
| {
condition?:
| boolean
| "all"
| "some"
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
},
) => boolean)
| undefined;
filename?: string | ((fileData: any) => string) | undefined;
banner?:
string | boolean | ((commentsFile: string) => string) | undefined;
};Default: true
Whether comments shall be extracted to a separate file, (see details).
By default, extract only comments using /^\**!|@preserve|@license|@cc_on/i RegExp condition and remove remaining comments.
If the original file is named foo.js, then the comments will be stored to foo.js.LICENSE.txt.
A minimizer's options.format.comments specifies whether the comment will be preserved - i.e., it is possible to preserve some comments (e.g. annotations) while extracting others, or even preserve comments that have already been extracted.
boolean
Enable/disable extracting comments.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
extractComments: true,
}),
],
},
};string
Extract all or some (use the /^\**!|@preserve|@license|@cc_on/i RegExp) comments.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
extractComments: "all",
}),
],
},
};RegExp
All comments that match the given expression will be extracted to a separate file.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
extractComments: /@extract/i,
}),
],
},
};function
All comments that match the given expression will be extracted to a separate file.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
extractComments: (astNode, comment) => {
if (/@extract/i.test(comment.value)) {
return true;
}
return false;
},
}),
],
},
};object
Allows you to customize condition for extracting comments, and specify the extracted file name and banner.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
extractComments: {
condition: /^\**!|@preserve|@license|@cc_on/i,
filename: (fileData) =>
// The "fileData" argument contains object with "filename", "basename", "query" and "hash"
`${fileData.filename}.LICENSE.txt${fileData.query}`,
banner: (licenseFile) =>
`License information can be found in ${licenseFile}`,
},
}),
],
},
};condition
Type:
type condition =
| boolean
| "all"
| "some"
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
},
) => boolean)
| undefined;The condition that determines which comments should be extracted.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
extractComments: {
condition: "some",
filename: (fileData) =>
// The "fileData" argument contains object with "filename", "basename", "query" and "hash"
`${fileData.filename}.LICENSE.txt${fileData.query}`,
banner: (licenseFile) =>
`License information can be found in ${licenseFile}`,
},
}),
],
},
};filename
Type:
type filename = string | ((fileData: any) => string) | undefined;Default: [file].LICENSE.txt[query]
Available placeholders: [file], [query] and [filebase] ([base] for webpack 5).
The file where the extracted comments will be stored.
Default is to append the suffix .LICENSE.txt to the original filename.
Warning
We highly recommend using the
.txtextension. Using.js/.cjs/.mjsextensions may conflict with existing assets, which leads to broken code.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
extractComments: {
condition: /^\**!|@preserve|@license|@cc_on/i,
filename: "extracted-comments.js",
banner: (licenseFile) =>
`License information can be found in ${licenseFile}`,
},
}),
],
},
};banner
Type:
type banner = string | boolean | ((commentsFile: string) => string) | undefined;Default: /*! For license information please see ${commentsFile} */
The banner text that points to the extracted file and will be added at the top of the original file.
It can be false (no banner), a String, or a function<(string) -> String> that will be called with the filename where the extracted comments have been stored.
The banner will be wrapped in a comment: /*! … */ by default, or whatever the minify function's formatBanner helper returns. An HTML minimizer needs one, since a /*! line is text in a document:
minify.formatBanner = (banner) => `<!-- ${banner} -->`;The banner goes at the top of the asset; a getBannerPosition helper returning "end" appends it instead, which keeps a document's doctype first:
minify.getBannerPosition = () => "end";The source of both helpers is part of the cache key and the [chunkhash], but not the values they close over: a helper whose output depends on such a value must make getMinimizerVersion change with it, or a rebuild reuses the earlier banner from the cache.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
extractComments: {
condition: true,
filename: (fileData) =>
// The "fileData" argument contains object with "filename", "basename", "query" and "hash"
`${fileData.filename}.LICENSE.txt${fileData.query}`,
banner: (commentsFile) =>
`My custom banner about license information ${commentsFile}`,
},
}),
],
},
};Embedded source
Source written in one language that reaches the bundle inside another is
minified too, which no asset carries and an asset-level minimizer therefore
never sees. It needs no option (webpack >= 5.110.0; older versions reach only
what an asset nests inside itself).
Such source has no filename, so test / include / exclude and each
minimizer's filter — all of which match filenames — cannot dispatch it.
It is dispatched by the language it is written in, which every minify
function states for itself:
myCssMinifier.getTypes = () => ["css"];webpack's own cssMinify and htmlMinify declare css and html, and are
the two that hand out what they nest. The built-ins declare javascript
(terserMinify, uglifyJsMinify, swcMinify, esbuildMinify), css
(cssnanoMinify, cssoMinify, cleanCssMinify, esbuildMinifyCss,
lightningCssMinify, swcMinifyCss), html (htmlMinifierTerser,
swcMinifyHtml, swcMinifyHtmlFragment, minifyHtmlNode) and json
(jsonMinify). A language no configured minimizer
claims — svg out of the box — is emitted exactly as it was written, and a
custom minify function without getTypes is never handed embedded source at
all.
What this reaches:
- CSS and HTML a module embeds in a JavaScript string literal (every
exportTypebutlink). - The text an
asset/sourcemodule embeds, and the payload anasset/inlinemodule encodes — the payload before it is encoded, so the encoding covers what came back. A language written as text only: an inlinesvgis offered, an inlinepngorjpegis not, so a raster image that becomes adata:URI is minified bygeneraterather than here. - What a document or a stylesheet nests inside itself: an inline
<style>, everystyle="", a<script>holding JavaScript or JSON, an<svg>subtree, the document an<iframe srcdoc>holds, and the payload of aurl()data:URL. This is how an inline<script>is minified byterserat all.
A style="" arrives as css with as: "block-contents" — the same word
module.parser.css.as uses. It holds a block's contents rather than a whole
stylesheet, so a minifier claiming css is handed one either way and as says
which it is. Ignore it at your peril: parsing a declaration list as a stylesheet
finds no rule and returns nothing.
A <script type="module"> arrives the same way, as javascript with
as: "module": JavaScript has two productions too, and a module script is one
wherever it sits. The built-in JavaScript minimizers read it as their own
module option, so the engine is never handed the word itself; a minify
function of your own claiming javascript is handed it and decides. Ignore it
at your peril: a module script read as a classic one is a syntax error the
moment it holds a top-level await.
An event handler attribute — onclick="" — arrives as javascript with
as: "event-handler": its value is a function body, the production the
return cancelling an event is written in. No JavaScript engine here parses one
on its own, so each built-in minimizer minifies the function that body belongs
to and answers with the body back out of it. A minify function of your own is
handed the body as written and reads it however its engine can.
The nested case needs a minifier that can hand its nested bodies out, which it
also states for itself — webpack's cssMinify and htmlMinify do:
// The languages this minifier can offer, given the options it will run with.
myHtmlMinifier.getEmbeddedTypes = (minimizerOptions) => ["css", "javascript"];The option is passed only when the two declarations meet: if nothing configured claims a language this minifier could offer, it is never handed one, and minification is exactly what it always was. When it is, the nested bodies are reached in the same parse that prints — the minifier leaves a marker where each one goes and the answers are put in their place, so nothing is parsed twice.
Note
Source a module embeds in JavaScript is minified during code generation, before the worker pool is up, so
paralleldoes not apply to it. What an asset nests inside itself is minified in the pool with the asset.
Examples
Preserve Comments
Extract all legal comments (i.e. /^\**!|@preserve|@license|@cc_on/i) and preserve /@license/i comments.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.terserMinify,
options: { format: { comments: /@license/i } },
},
extractComments: true,
}),
],
},
};Remove Comments
If you want to build without comments, use this config:
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.terserMinify,
options: { format: { comments: false } },
},
extractComments: false,
}),
],
},
};uglify-js
UglifyJS is a JavaScript parser, minifier, compressor and beautifier toolkit.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.uglifyJsMinify,
// `options` will be passed to `uglify-js`
// Link to options - https://github.com/mishoo/UglifyJS#minify-options
options: {},
},
}),
],
},
};swc
swc is a super-fast compiler written in Rust, producing widely supported JavaScript from modern standards and TypeScript.
Warning
extractCommentsis supported with@swc/core >= 1.15.30. Only serializable extract conditions are supported: booleans,"some","all", string patterns,RegExpvalues without flags, or object conditions that resolve to those forms. Function conditions and flagged regular expressions are not supported.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.swcMinify,
// `options` will be passed to `swc` (`@swc/core`)
// Link to options - https://swc.rs/docs/config-js-minify
options: {},
},
}),
],
},
};esbuild
esbuild is an extremely fast JavaScript bundler and minifier.
Warning
The
extractCommentsoption is not supported, and all legal comments (i.e. copyright, licenses and etc) will be preserved.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.esbuildMinify,
// `options` will be passed to `esbuild`
// Link to options - https://esbuild.github.io/api/#minify
// Note: the `minify` options is true by default (and override other `minify*` options), so if you want to disable the `minifyIdentifiers` option (or other `minify*` options) please use:
// options: {
// minify: false,
// minifyWhitespace: true,
// minifyIdentifiers: false,
// minifySyntax: true,
// },
options: {},
},
}),
],
},
};JSON
Uses JSON.stringify() to minify your JSON files during the build process.
webpack.config.js
module.exports = {
optimization: {
minimize: true,
minimizer: [
// Keeps original terser plugin to minify JS files
"...",
// Will minify JSON files (they can come from copy-webpack-plugin or when you are using asset modules)
new MinimizerPlugin({
test: /\.json$/,
minify: {
implementation: MinimizerPlugin.jsonMinify,
// We are supporting `space` and `replacer` options, you can set them below
options: {},
},
}),
],
},
};HTML
The plugin can minify HTML assets too. Pick one of the bundled HTML
minimizers and set test to match your HTML files.
Available HTML minimizers:
webpack.html.syntax.htmlMinify— webpack's own, shipped with webpack itself, so it needs no extra dependency. The one that can minify what a document nests — an inline<style>, a<script>, an<svg>. See webpack's own minimizers.MinimizerPlugin.htmlMinifierTerser— useshtml-minifier-terser.MinimizerPlugin.swcMinifyHtml— uses@swc/htmlfor full HTML documents (with doctype and<html>/<head>/<body>tags).MinimizerPlugin.swcMinifyHtmlFragment— uses@swc/htmlfor HTML fragments (e.g. content inside<template></template>or partial HTML strings).MinimizerPlugin.minifyHtmlNode— uses@minify-html/node.
The HTML minimizers are optional peer dependencies — install only the one you actually use:
npm install --save-dev html-minifier-terser
# or
npm install --save-dev @swc/html
# or
npm install --save-dev @minify-html/node
Note
HTML assets typically come from plugins like
copy-webpack-plugin,html-webpack-plugin, or webpack's asset modules.
Note
Whitespace handling differs between tools (defaults):
@swc/html— removes/collapses whitespace only in safe places (aroundhtml/body, inside<head>, between<meta>/<script>/<link>etc.).html-minifier-terser— always collapses multiple whitespaces to a single space (never removes entirely); configurable via its options.@minify-html/node— see its whitespace docs.
html-minifier-terser
html-minifier-terser is a JavaScript-based HTML minifier with no native dependency.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
// Keeps the default Terser plugin for JS files
"...",
new MinimizerPlugin({
test: /\.html(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.htmlMinifierTerser,
// Options - https://github.com/terser/html-minifier-terser#options-quick-reference
options: {
collapseWhitespace: true,
removeComments: true,
},
},
}),
],
},
};@swc/html — HTML documents
Use swcMinifyHtml for complete HTML documents (i.e. with a doctype and <html>/<head>/<body> tags).
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.html(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.swcMinifyHtml,
// Options - https://github.com/swc-project/bindings/blob/main/packages/html/index.ts
options: {},
},
}),
],
},
};@swc/html — HTML fragments
Use swcMinifyHtmlFragment for partial HTML — for example, content of <template></template> tags or HTML strings that get injected into another document.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.template\.html$/i,
minify: {
implementation: MinimizerPlugin.swcMinifyHtmlFragment,
// Options - https://github.com/swc-project/bindings/blob/main/packages/html/index.ts
options: {},
},
}),
],
},
};Note
The difference between
swcMinifyHtmlandswcMinifyHtmlFragmentis the error reporting — invalid or broken syntax is reported at build time.
@minify-html/node
@minify-html/node is a Rust HTML minifier.
webpack.config.js
const Minimizer = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new Minimizer({
test: /\.html(\?.*)?$/i,
minify: {
implementation: Minimizer.minifyHtmlNode,
// Options - https://github.com/wilsonzlin/minify-html#minification
options: {},
},
}),
],
},
};You can also stack multiple MinimizerPlugin instances to compress different files with different minify functions in the same build (e.g. JS with terserMinify, HTML with htmlMinifierTerser, JSON with jsonMinify).
CSS
The plugin can minify CSS assets too. Pick one of the bundled CSS
minimizers and set test to match your CSS files.
Available CSS minimizers:
webpack.css.syntax.cssMinify— webpack's own, shipped with webpack itself, so it needs no extra dependency. The one that can minify the CSS a document nests. See webpack's own minimizers.MinimizerPlugin.cssnanoMinify— usescssnano(viapostcss).MinimizerPlugin.cssoMinify— usescsso.MinimizerPlugin.cleanCssMinify— usesclean-css.MinimizerPlugin.esbuildMinifyCss— usesesbuildwith the CSS loader.MinimizerPlugin.lightningCssMinify— useslightningcss.MinimizerPlugin.swcMinifyCss— uses@swc/css.
The CSS minimizers are optional peer dependencies — install only the ones you actually use:
npm install --save-dev cssnano postcss
# or
npm install --save-dev csso
# or
npm install --save-dev clean-css
# or
npm install --save-dev esbuild
# or
npm install --save-dev lightningcss
# or
npm install --save-dev @swc/css
Note
CSS assets typically come from plugins like
mini-css-extract-pluginor webpack's asset modules.
cssnano
cssnano runs as a PostCSS plugin.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
// Keeps the default Terser plugin for JS files
"...",
new MinimizerPlugin({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.cssnanoMinify,
// Options - https://cssnano.github.io/cssnano/docs/config-file/
options: {
preset: "default",
},
},
}),
],
},
};csso
csso is a CSS minifier with structural optimisations.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.cssoMinify,
// Options - https://github.com/css/csso#minifysource-options
options: {},
},
}),
],
},
};clean-css
clean-css is a widely-used CSS optimiser.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.cleanCssMinify,
// Options - https://github.com/clean-css/clean-css#constructor-options
options: {},
},
}),
],
},
};esbuild
esbuild ships with a fast CSS minifier (used via its CSS loader).
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.esbuildMinifyCss,
// Options - https://esbuild.github.io/api/#transform-api
options: {},
},
}),
],
},
};lightningcss
lightningcss is a Rust-based CSS parser, transformer, and minifier.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.lightningCssMinify,
// Options - https://lightningcss.dev/transpilation.html
options: {},
},
}),
],
},
};@swc/css
@swc/css is a Rust-based CSS minifier.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.swcMinifyCss,
// Options - https://github.com/swc-project/bindings/blob/main/packages/css/index.ts
options: {},
},
}),
],
},
};webpack's own minimizers
webpack ships a CSS and an HTML minimizer of its own. They need no extra
dependency — if you have webpack, you have them — and they are the two that
can hand out what a document or a stylesheet nests inside itself, so pairing
them with terserMinify and jsonMinify is what minifies embedded source
(see Embedded source). They are reached through webpack's
public css.syntax and html.syntax exports, and need webpack 5.111.0 or
newer.
const MinimizerPlugin = require("minimizer-webpack-plugin");
const { cssMinify } = require("webpack").css.syntax;
const { htmlMinify } = require("webpack").html.syntax;
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
test: /\.(?:[cm]?js|css|html|json)(\?.*)?$/i,
minify: [
{ implementation: MinimizerPlugin.terserMinify },
{ implementation: cssMinify },
{ implementation: htmlMinify },
{ implementation: MinimizerPlugin.jsonMinify },
],
}),
],
},
};Each one dispatches itself, so test only has to be wide enough to let the
assets through: cssMinify claims *.css and htmlMinify claims *.html
through their own filter, and both run in the worker pool.
cssMinify | htmlMinify | |
|---|---|---|
getTypes() | css | html |
getEmbeddedTypes() | svg, css, html, json, javascript | css, javascript, json, svg, html |
filter | /\.css(\?.*)?$/i | /\.html(\?.*)?$/i |
supportsWorkerThreads() | true | true |
Their options are optimization.minimize.css and
optimization.minimize.html respectively. environment carries what the
target can read ({ browsers, vendorPrefixes }, the CSS entries of
output.environment),
so a spelling the target could not parse is never reached for.
cssMinify options
Every option is named directly on the object — the per-transform switches sit beside the rest, not nested.
| Option | Type | Default | What it does |
|---|---|---|---|
environment | { browsers?: string[], vendorPrefixes?: boolean } | — | What the target can read. browsers is a browserslist selection; vendorPrefixes: false leaves prefixes alone. |
as | "stylesheet" | "block-contents" | "stylesheet" | Which production to read the source as. The plugin sets it — a style="" arrives as "block-contents". |
convertLengthUnits | boolean | false | Rewrite a length into a shorter unit it is exactly equal in (16px → 1pc). Off because it earns nothing once compressed. |
rewriteCustomProperties | boolean | false | Shorten a custom property's value like any other (--x:#ffffff → --x:#fff). Off because getPropertyValue() hands that text back. |
unusedSymbols | string[] | — | Names a whole-project analysis found unused; a bare name is a class, id or @keyframes, a ---prefixed one a custom property. |
pseudoClasses | { [name: string]: string } | — | Write a pseudo-class as a class instead, so a script can apply it where the engine does not. |
renderEmbeddedSource | function | — | Owned by the plugin — do not pass it. |
Per-transform switches, each on unless set to false:
| Switch | What it rewrites |
|---|---|
colorFallbacks | Writes a color the target cannot read as an extra declaration before it, in a spelling it does read. |
lowerUnsupported | Writes a spelling the target cannot read as one it can — the same value, said another way. |
foldCase | Lowercases a name that matches ASCII case-insensitively (at-rule, property, pseudo, function, unit, keyword). |
rewriteEscapes | Writes an escaped identifier the shortest way that names it. |
comments | Which comments survive: "some" (default), true / "all", false, or a pattern / predicate over the comment's text. |
resolveCustomAtRules | Inlines what @custom-media / @custom-selector names, and drops the rule that named it. |
mergeLonghands | Writes a family of longhands as the one shorthand that sets them. |
mergeRules | Joins rules printing the same block, at-rules sharing a prelude, and a @layer block a later sibling reopens. |
normalizeQuotes | Normalizes quoting of strings, url(), font families and attribute values. |
reduceFunctions | Computes a call into the shorter call naming the same value (calc(), transforms, gradients, easings, filters). |
removeDeadRules | Drops a rule or declaration nothing can read — an empty rule, one an identical later one supersedes. |
rewriteDirSelector | Writes a :dir() the target cannot read as the [dir] attribute selector. |
shortenColors | Writes each color in the shortest spelling of the same value. |
shortenMediaQueries | Writes a media feature in its range spelling and collapses an and of two into the interval. |
shortenNumbers | Writes each number in its shortest equal spelling. |
shortenSelectors | Rewrites a selector into a shorter equal one. |
shortenValues | Writes a value the shortest way its property's own grammar allows. |
htmlMinify options
| Option | Type | Default | What it does |
|---|---|---|---|
environment | same as cssMinify | — | CSS's, not HTML's: handed to the CSS minifier this runs over inline CSS. |
css | object | {} | The CSS minifier's options for that inline CSS — see below. |
collapseWhitespace | boolean | "conservative" | "smart" | "all" | false | Collapses each run of whitespace in text, except where an ancestor renders it verbatim; "smart" also drops what sits against a block edge, "all" every edge. |
removeEmptyAttributes | boolean | false | Drops an attribute whose empty value leaves it in the state its absence gives. |
removeEmptyElements | boolean | false | Drops an element with no children and no attributes, unless its bare form is meaningful. |
mergeStyles | boolean | false | Prints a run of adjacent <style> elements as one sheet. |
sortAttributes | boolean | false | Prints attributes commonest name first, ties by name — nothing in HTML reads the order. |
sortTokenLists | boolean | false | Prints every token list the DOM reads as a set (class, rel, part, …) in token order. |
removeRedundantAttributes | boolean | "smart" | "all" | false | Drops an attribute whose value is the element's own default; "all" also drops spec defaults a selector can match. |
removeImpliedTags | boolean | "smart" | "all" | "smart" | How much of the <html> / <head> / <body> shell may be left out: "smart" only the <html> start tag, true / "all" all six. |
minifyConditionalComments | boolean | false | Minifies the markup inside a conditional comment's body. |
renderEmbeddedSource | function | — | Owned by the plugin — do not pass it. |
Per-transform switches, each on unless set to false:
| Switch | What it rewrites |
|---|---|
collapseBooleanAttributes | Writes a boolean attribute spelled with its own name (disabled="disabled") as the bare name. |
comments | Which comments survive — same values as CSS's. One a parser or server reads is kept regardless. |
normalizeAttributeQuotes | Drops or re-picks an attribute value's quotes. |
normalizeEnumeratedAttributes | Folds an enumerated value to the keyword it names. |
normalizeListAttributes | Normalizes a space-, comma- or descriptor-separated list (class, rel, srcset, sizes, viewport content). |
normalizeNumericAttributes | Writes an integer attribute the one way its rules read it. |
removeOptionalTags | Leaves out an optional tag other than the shell, which is removeImpliedTags. |
The css object takes the CSS minifier's convertLengthUnits,
rewriteCustomProperties, unusedSymbols and pseudoClasses, plus the CSS
per-transform switches. environment is not among them: htmlMinify runs
the CSS minifier over every inline <style> and style="", so environment
stays top level, shared by the document and the CSS inside it. Give the two
minimizers the same environment and a .css asset and the same declaration
inline agree about the target:
const environment = { browsers: ["chrome 100", "safari 15"] };
const options = [
{},
// cssMinify
{ environment, convertLengthUnits: true },
// htmlMinify — `environment` reaches the CSS it nests; `css` carries the rest
{ environment, css: { convertLengthUnits: true } },
{},
];Note
Do not pass
renderEmbeddedSourceyourself. The plugin owns that option: it wires the minimizers to each other with it, and a value you set is replaced. What a minimizer offers and what another claims is what routes embedded source — see Embedded source.
Both minimizers re-resolve webpack when they run, in the worker pool as well
as in the main process, so the webpack resolvable from this plugin has to be
the one they came from, and at least 5.111.0 — the release that exports them
on webpack.css.syntax and webpack.html.syntax. On an older webpack the
destructure above yields undefined; a second or older copy in the tree
surfaces from inside the minifier as:
Cannot destructure property 'SourceProcessor' of 'webpack.css.syntax' as it is undefined.
Cannot destructure property 'askEmbeddedRenderer' of 'webpack.html.syntax' as it is undefined.
Deduplicate webpack (npm dedupe, or a single version in a workspace root) and
it goes away.
Images
The plugin can minify image assets too. Pick one of the bundled image
minimizers and set test to match your images.
Available image minimizers:
MinimizerPlugin.sharpMinify— usessharp, re-encoding each image as its own format.MinimizerPlugin.svgoMinify— usessvgo.MinimizerPlugin.imageminMinify— usesimageminand the plugins you name.MinimizerPlugin.napiRsImageMinify— uses@napi-rs/image, which ships prebuilt codecs.
Converting an image to another format is generate's job rather
than a minimizer's, since only that can rename the asset:
MinimizerPlugin.sharpGenerate and MinimizerPlugin.imageminGenerate.
The image minimizers are optional peer dependencies — install only the ones you actually use:
npm install --save-dev sharp
# or
npm install --save-dev svgo
# or
npm install --save-dev imagemin imagemin-mozjpeg imagemin-pngquant
Note
On
minifythese only minify — they never change an image's format or name. An asset's name is decided during code generation, long before a minimizer runs, so a renamed asset would leave the bundle pointing at a URL nothing emits. Converting.pngto.webpis whatgenerateis for: it runs while the module builds, early enough to rename.
Note
sharpandimageminread the asset's bytes rather than its text, so they run in the webpack process rather than in the worker pool — they do their own threading. Minimizers configured beside them keep the pool.
Images that never become files
minify runs over emitted assets, so an image that becomes a data: URI
instead of a file — asset/inline, or asset under
Rule.parser.dataUrlCondition
— never reaches it. An SVG is the exception, since it is text and webpack
offers it as embedded source.
Put the same minimizer under generate and it does reach one: a
generator runs while the module builds, over the bytes themselves, before
anything encodes them. It renames nothing unless it answers with a name, so a
minimizer stays a minimizer there.
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
generate: { implementation: MinimizerPlugin.sharpMinify },
});Do not list the same image minimizer in both for the same assets — an emitted
one would then be re-encoded twice, which costs time and, for a lossy format,
quality. generate covers inlined and emitted images alike; minify is the
one to keep when nothing is inlined, since it runs after the whole build and
caches per asset. An "import" generator needs webpack 5.111 or newer.
Lossless and lossy
Images are optimized in one of two modes:
- Lossless — the decoded pixels come back identical, only the encoding is made smaller.
- Lossy — quality is traded for a smaller file.
Which one you get with no encodeOptions at all, measured by decoding the
output back to pixels and comparing it with the input:
| Format | sharpMinify | napiRsImageMinify |
|---|---|---|
avif | lossy | lossy |
gif | lossy | not handled |
jpeg | lossy | lossless |
png | lossless | lossless |
tiff | lossy | not handled |
webp | lossy | lossy |
So sharpMinify is lossy by default everywhere except PNG — it re-encodes at
sharp's own defaults, which are quality settings rather than lossless ones.
napiRsImageMinify is lossless by default for both PNG and JPEG, because those
two are repacked in place by oxipng and mozjpeg, rewriting the encoded bytes
rather than decoding to pixels and encoding again.
svgoMinify rewrites markup rather than pixels. It is lossless in the sense
that the rendered image is meant to be unchanged, but its default plugin preset
does rewrite paths and drop metadata — see
svgo for which of those to turn
off.
imageminMinify is whatever its plugins are. imagemin-jpegtran,
imagemin-optipng and imagemin-gifsicle are lossless; imagemin-mozjpeg and
imagemin-pngquant are lossy. imagemin-svgo can be either.
Asking sharpMinify for lossless output
new MinimizerPlugin({
test: /\.(png|jpe?g|webp|avif)(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.sharpMinify,
options: {
encodeOptions: {
// https://sharp.pixelplumbing.com/api-output
jpeg: { quality: 100 },
webp: { lossless: true },
avif: { lossless: true },
// PNG is already lossless at sharp's defaults
png: {},
},
},
},
});Asking napiRsImageMinify for lossy output, which is where the savings are:
new MinimizerPlugin({
test: /\.(png|jpe?g|webp|avif)(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.napiRsImageMinify,
options: {
encodeOptions: {
// Anything below 100 re-encodes rather than repacking
jpeg: { quality: 80 },
webp: { quality: 80 },
avif: { quality: 70 },
// `png` has no quality setting — it is lossless either way
},
},
},
});Note
Lossless does not mean "no smaller". On this repository's own PNG fixture
napiRsImageMinifyreturns 59% of the original size without touching a single pixel, andsharpMinifyreturns 92%.
sharp
sharp re-encodes each image as the format
its name already claims, so a .png stays a PNG. It handles avif, gif,
heif, jp2, jpeg, png, tiff and webp.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
// Keeps the default Terser plugin for JS files
"...",
new MinimizerPlugin({
test: /\.(png|jpe?g|webp|avif|tiff?|gif)$/i,
minify: {
implementation: MinimizerPlugin.sharpMinify,
options: {
// Options are keyed by sharp's format name
// https://sharp.pixelplumbing.com/api-output
encodeOptions: {
jpeg: { quality: 80 },
png: { compressionLevel: 9 },
webp: { lossless: true },
},
},
},
}),
],
},
};sharp can also transform the image while it re-encodes:
new MinimizerPlugin({
test: /\.(png|jpe?g)$/i,
minify: {
implementation: MinimizerPlugin.sharpMinify,
options: {
// `enabled` and `unit` ("px" by default, or "percent") are read here;
// everything else goes to sharp
// https://sharp.pixelplumbing.com/api-resize
resize: { width: 800, unit: "px", fit: "inside" },
// A number of degrees, or "auto" to follow the EXIF orientation
rotate: "auto",
flip: false,
flop: false,
grayscale: false,
// A sigma, or true for a fast default
blur: false,
sharpen: false,
encodeOptions: { jpeg: { quality: 80 } },
},
},
});Each of these can also be asked for by an asset's own name — see Query strings in asset names.
svgo
svgo minifies SVG. It also reaches an
asset/inline SVG — one that becomes a data: URI rather than a file — which
carries no asset name for test to match; see
Embedded source.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.svg(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.svgoMinify,
options: {
// Options - https://github.com/svg/svgo#configuration
encodeOptions: {
multipass: true,
plugins: ["preset-default"],
},
},
},
}),
],
},
};imagemin
imagemin runs the plugins you name.
Each is a string — "mozjpeg" resolves imagemin-mozjpeg — or a
[name, options] pair. Install each plugin yourself.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.(png|jpe?g|gif|svg)$/i,
minify: {
implementation: MinimizerPlugin.imageminMinify,
options: {
plugins: [
"gifsicle",
"mozjpeg",
["pngquant", { quality: [0.6, 0.8] }],
"svgo",
],
},
},
}),
],
},
};Note
A plugin that converts —
imagemin-webp,imagemin-avif— writes a format the asset's name does not claim.imageminMinifykeeps the original and warns instead of writing it, since it cannot rename the asset. Reach forgeneratewithMinimizerPlugin.imageminGenerateto convert: it runs the same plugins while the module builds, which is where the asset can still take the name of the format they wrote.
@napi-rs/image
@napi-rs/image is a set of Rust
codecs shipped as prebuilt binaries, so it needs no system library. It handles
avif, jpeg, png and webp — the formats it both reads back reliably and
makes smaller.
Each format is re-encoded as itself, using the codec that does that best:
png goes through oxipng, which
rewrites the container losslessly and leaves the pixels untouched, and jpeg
through mozjpeg. Decoding either to
pixels and re-encoding saves a small fraction as much.
webpack.config.js
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
"...",
new MinimizerPlugin({
test: /\.(png|jpe?g|webp|avif)$/i,
minify: {
implementation: MinimizerPlugin.napiRsImageMinify,
options: {
// Options are keyed by the format's own name
// https://github.com/Brooooooklyn/Image#usage
encodeOptions: {
// `PNGLosslessOptions`
png: { force: true },
// `JpegCompressOptions`
jpeg: { quality: 80 },
// `AvifConfig`
avif: { quality: 70, speed: 4 },
// The quality factor, 0-100
webp: { quality: 80 },
},
},
},
}),
],
},
};Note
pngis recompressed losslessly, so it takes no quality setting. Resizing and rotating are not exposed here — usesharpfor those.
Query strings in asset names
An asset keeps the query and fragment of the request that made it:
output.assetModuleFilename is [hash][ext][query][fragment] by default, so
import icon from "./icon.svg?v=2" is emitted as <hash>.svg?v=2.
test, include and exclude are read against both spellings — the whole name
and the name with the query and fragment stripped — so all three of these accept
that asset:
test: /\.svg$/i; // names the file
test: /\.svg(\?.*)?$/i; // names the query form
test: /\?v=2$/; // names the query itselfexclude rejects on either spelling, so excluding by file name also excludes
the queried asset. Every built-in minimizer's own filter reads past the query
in the same way.
Asking sharp for something from the import. sharpMinify reads what to do
off the asset's name, so one image can be sized, turned or re-encoded where it
is imported rather than in the configuration:
import banner from "./banner.png?width=320&quality=80";| Query | Short | Value | What it does |
|---|---|---|---|
width | w | pixels, or auto | target width |
height | h | pixels, or auto | target height |
unit | u | px (default) or percent | how width and height are read |
fit | - | cover (default), contain, fill, … | what to do when both dimensions are given |
position | pos | centre (default), right top, entropy, … | where to crop from |
background | bg | a colour | what contain pads with |
without-enlargement | - | flag | never scale the image up |
rotate | rot | degrees, or auto | turn by an angle, or by the EXIF orientation |
flip | - | flag | mirror vertically |
flop | - | flag | mirror horizontally |
grayscale | gray | flag | drop the colour |
blur | - | sigma, or flag | blur |
sharpen | - | sigma, or flag | sharpen |
quality | q | a number | encode quality |
lossless | - | flag | encode losslessly, where the format has one |
effort | - | a number | how hard the encoder tries |
progressive | prog | flag | interlace, where the format has it |
| Import | What comes out |
|---|---|
./photo.jpg?w=64&h=64&fit=cover&pos=entropy | a 64x64 thumbnail, cropped to the busiest part |
./photo.jpg?width=16&blur&quality=30 | a tiny blurred placeholder |
./logo.png?width=50&unit=percent&grayscale | half-size and grey |
A flag is on when it is present — ?flip — and reads true/1/yes the
same way, false/0/no the other. greyscale and grey spell grayscale,
auto on width or height drops one set in the minimizer's options, and a
parameter can be spelled in any case.
Every one of these can also be set in the minimizer's options; the name wins where
both say something, being the more specific of the two. resize: { enabled: false } still turns resizing off entirely.
Two rules decide what happens to a value:
- Its shape is checked here, and anything else is ignored. An asset's name
carries queries this plugin never put there, so
?v=2must not become a resize — and neither does?width=nonsenseor?flip=maybe. - Whether a well-formed value is one sharp accepts is sharp's to report, and
it answers per format:
effortruns to 6 for webp and 9 for avif,qualitystarts at 0 for png and 1 for jpeg, and a colour is whatever it can parse. A value outside those fails the build with sharp's own message rather than being dropped silently. An option a format has no use for —?losslesson a jpeg — is simply ignored.
napiRsImageMinify reads the same names, as far as @napi-rs/image goes:
width/w, height/h, fit (cover, fill, inside), filter
(nearest, triangle, catmull-rom, gaussian, lanczos3), rotate/rot,
flip, flop, grayscale, invert, blur, quality/q, lossless and
speed. Three differences are worth knowing:
rotatetakes a quarter turn, not any angle —90,180,270, a negative or a multiple of those, orautofor the EXIF orientation. napi applies one orientation, so mirroring and a turn compose into it (?flip&flopis a half turn); asking forautoalongside an explicit turn warns, because only one of them can be applied.- Every value is checked here, rather than left to the library as sharp's
are. napi rejects an unknown
fitwithout naming it, and answers a quality of 150 by silently writing a bigger file, so a value it would mishandle is dropped instead. - Transforming costs the repack. This minimizer's advantage is rewriting
encoded bytes — oxipng saves 99% of a png where decoding and re-encoding saves
9% — so when a query makes a transform necessary, its output is handed back to
the repack rather than replacing it. A
?rotate=autoon an image whose EXIF asks for nothing skips the decode entirely.
svgoMinify reads precision (or floatPrecision, 0–10), multipass,
pretty and indent, so one SVG can be laid out readably or coarsened
without a second configuration:
import icon from "./icon.svg?precision=1";imageminMinify reads nothing. Its options are a list of already-configured
plugins, each with option names of its own, so there is no parameter a query
could set that would mean the same thing twice over.
Note
Two assets whose names differ only by their query would be written to the same file, and webpack refuses that. To emit several sizes of one image, give each its own file name and keep the query on the asset name:
module.exports = { output: { assetModuleFilename: (pathData) => { const query = pathData.module.resourceResolveData.query || ""; const name = query.replace(/^\?/, "-").replace(/[=&]/g, "-"); return `[name]${name}[ext][query]`; }, }, };Without the trailing
[query]the size is no longer on the name and nothing resizes; without the first half the two sizes collide.
Note
A query cannot pick a format on this path. Resizing needs no new name, which is why it works here; changing the format does, and
minifyruns after names are decided. Hand?as=webptogenerateinstead, which sees the module as it builds and can emit the new format under a new name.
Images beside everything else
minify takes an array, and each minimizer is offered only the assets its own
filter accepts, so one plugin instance can cover several languages at once:
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
test: /\.(js|css|svg|png|jpe?g)$/i,
minify: [
{ implementation: MinimizerPlugin.terserMinify },
{ implementation: MinimizerPlugin.cssnanoMinify },
{ implementation: MinimizerPlugin.svgoMinify },
{
implementation: MinimizerPlugin.sharpMinify,
options: { encodeOptions: { png: { compressionLevel: 9 } } },
},
],
}),
],
},
};Custom Minify Function
Override the default minify function - use uglify-js for minification.
webpack.config.js
const minifyWithUglifyJs = (file, sourceMap, minimizerOptions) => {
const uglifyJsOptions = { ...minimizerOptions };
// The plugin fills these two in, and `uglify-js` rejects options it does not know
delete uglifyJsOptions.ecma;
delete uglifyJsOptions.module;
if (sourceMap) {
uglifyJsOptions.sourceMap = { content: sourceMap };
}
return require("uglify-js").minify(file, uglifyJsOptions);
};
// Which assets it is offered, and which language it minifies. Without
// `getTypes` it is never handed the JavaScript a document or a module embeds
minifyWithUglifyJs.filter = (name) => /\.[cm]?js(\?.*)?$/i.test(name);
minifyWithUglifyJs.getTypes = () => ["javascript"];
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: minifyWithUglifyJs,
// https://github.com/mishoo/UglifyJS2#minify-options
options: {/* your `uglify-js` package options */},
},
}),
],
},
};A minifier that nests other languages inside its own output hands each body
out with renderEmbeddedSource rather than minifying it itself — see
minify and Embedded source.
Typescript
With default Terser minify function:
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
minify: {
implementation: MinimizerPlugin.terserMinify,
options: { compress: true },
},
}),
],
},
};With built-in minify functions:
import { type JsMinifyOptions as SwcOptions } from "@swc/core";
import { type MinifyOptions as SwcCssOptions } from "@swc/css";
import {
type FragmentOptions as SwcHtmlFragmentOptions,
type Options as SwcHtmlOptions,
} from "@swc/html";
import { type OptionsOutput as CleanCssOptions } from "clean-css";
import { type Options as CssnanoOptions } from "cssnano";
import { type CompressOptions as CssoOptions } from "csso";
import { type TransformOptions as EsbuildOptions } from "esbuild";
import { type Options as HtmlMinifierTerserOptions } from "html-minifier-terser";
import { type TransformOptions as LightningCssOptions } from "lightningcss";
import { type MinifyOptions as TerserOptions } from "terser";
import { type MinifyOptions as UglifyJSOptions } from "uglify-js";
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin<SwcOptions>({
minify: {
implementation: MinimizerPlugin.swcMinify,
options: {
// `swc` options
},
},
}),
new MinimizerPlugin<UglifyJSOptions>({
minify: {
implementation: MinimizerPlugin.uglifyJsMinify,
options: {
// `uglif-js` options
},
},
}),
new MinimizerPlugin<EsbuildOptions>({
minify: {
implementation: MinimizerPlugin.esbuildMinify,
options: {
// `esbuild` options
},
},
}),
// Alternative usage:
new MinimizerPlugin<TerserOptions>({
minify: {
implementation: MinimizerPlugin.terserMinify,
options: {
// `terser` options
},
},
}),
// HTML minimizers
new MinimizerPlugin<HtmlMinifierTerserOptions>({
test: /\.html(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.htmlMinifierTerser,
options: {
// `html-minifier-terser` options
},
},
}),
new MinimizerPlugin<SwcHtmlOptions>({
test: /\.html(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.swcMinifyHtml,
options: {
// `@swc/html` options
},
},
}),
new MinimizerPlugin<SwcHtmlFragmentOptions>({
test: /\.template\.html$/i,
minify: {
implementation: MinimizerPlugin.swcMinifyHtmlFragment,
options: {
// `@swc/html` fragment options
},
},
}),
// CSS minimizers
new MinimizerPlugin<CssnanoOptions>({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.cssnanoMinify,
options: {
// `cssnano` options
},
},
}),
new MinimizerPlugin<CssoOptions>({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.cssoMinify,
options: {
// `csso` options
},
},
}),
new MinimizerPlugin<CleanCssOptions>({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.cleanCssMinify,
options: {
// `clean-css` options
},
},
}),
new MinimizerPlugin<EsbuildOptions>({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.esbuildMinifyCss,
options: {
// `esbuild` options (CSS loader)
},
},
}),
new MinimizerPlugin<LightningCssOptions>({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.lightningCssMinify,
options: {
// `lightningcss` options
},
},
}),
new MinimizerPlugin<SwcCssOptions>({
test: /\.css(\?.*)?$/i,
minify: {
implementation: MinimizerPlugin.swcMinifyCss,
options: {
// `@swc/css` options
},
},
}),
],
},
};Migrating from image-minimizer-webpack-plugin
This plugin does what
image-minimizer-webpack-plugin
did, for every asset type rather than images alone, so one plugin covers a
build instead of two. The options line up like this:
image-minimizer-webpack-plugin | here |
|---|---|
minimizer | minify, same shape |
minimizer.implementation | implementation on that minimizer |
minimizer.options | options on that minimizer |
minimizer.filter | a filter on the minimizer itself |
generator[].implementation | generate |
generator[].options | options on that generator |
generator[].preset | the key the generator is written under |
generator[].type | type on that generator |
generator[].filename / .filter | filename / filter on that generator |
deleteOriginalAssets | deleteOriginalAssets on that generator |
concurrency | parallel |
test / include / exclude | unchanged |
loader | nothing — generate reaches a module without one |
severityError | nothing — a failed minimizer is an error |
The minimizers and generators keep their names —
imageminMinify, imageminGenerate, imageminNormalizeConfig, sharpMinify,
sharpGenerate, svgoMinify — so only the plugin they are read off changes.
squooshMinify and squooshGenerate are not carried over: @squoosh/lib is
unmaintained, and image-minimizer-webpack-plugin already marked both
deprecated.
A minimizer carries over as it is written: minimizer becomes
minify, with the same { implementation, options } object, and an
array of them stays an array.
Before
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
module.exports = {
plugins: [
new ImageMinimizerPlugin({
test: /\.(jpe?g|png)$/i,
minimizer: {
implementation: ImageMinimizerPlugin.sharpMinify,
options: { encodeOptions: { jpeg: { quality: 80 } } },
},
}),
],
};After
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
minify: {
implementation: MinimizerPlugin.sharpMinify,
options: { encodeOptions: { jpeg: { quality: 80 } } },
},
}),
],
},
};A generator is where two shapes changed rather than moved. Generators are
named here instead of listed, because a name is what ?as= asks for, so an
array of two generators becomes an object of two entries. And a generator that
produced a file beside the original is written with type: "asset", which is
the default there and not here — without it a generator re-encodes the module
itself, which is what lets the import be renamed.
Before
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
module.exports = {
plugins: [
new ImageMinimizerPlugin({
test: /\.(jpe?g|png)$/i,
generator: [
{
type: "asset",
preset: "webp",
implementation: ImageMinimizerPlugin.sharpGenerate,
options: { encodeOptions: { webp: { quality: 90 } } },
},
],
}),
],
};After
const MinimizerPlugin = require("minimizer-webpack-plugin");
module.exports = {
plugins: [
new MinimizerPlugin({
test: /\.(jpe?g|png)$/i,
generate: {
webp: {
type: "asset",
implementation: MinimizerPlugin.sharpGenerate,
options: { encodeOptions: { webp: { quality: 90 } } },
},
},
}),
],
};Contributing
We welcome all contributions! If you're new here, please take a moment to review our contributing guidelines before submitting issues or pull requests.
License
ModuleConcatenationPlugin
과거에 번들링을 할 때 webpack의 절충점 중 하나는 번들의 각 모듈이 개별 함수 클로저에 의해 래핑된다는 점이었습니다. 이러한 래퍼 함수는 브라우저에서 자바스크립트의 실행을 느리게 만들었습니다. 이와 달리, Closure Comiler와 RollupJS와 같은 도구는 모든 모듈을 하나의 클로저로 호이스팅 하거나 연결하여 코드가 브라우저에서 더 빠른 실행 시간을 갖게 했습니다.
이 플러그인은 위의 내용과 같은 연결 동작이 webpack에서 가능하게 해줍니다. 기본적으로 이 플러그인은 production mode에서는 이미 가능하고 나머지에서는 아직 가능하지 않습니다. 만약 생산 mode 최적화를 무효로 해야 하는 경우, optimization.concatenateModules 옵션을 false로 설정하세요. 다른 모드에서 연결 동작을 가능하게 하고 싶을 경우, ModuleConcatenationPlugin을 수동으로 설정하거나 optimization.concatenateModules 옵션을 사용하세요.
new webpack.optimize.ModuleConcatenationPlugin();이 연결 동작은 “범위 호이스팅” 이라고 불립니다.
범위 호이스팅은 특별히 ECMAScript Module 문법에서 사용 가능하도록 만들어진 형태입니다. 이로 인해 webpack은 현재 사용 중인 모듈의 종류와 다른 여러 조건에 따라 평범한 번들링으로 퇴화할 수도 있습니다.
Optimization Bailouts
글에서 설명하듯이 webpack은 부분적인 범위 호이스팅을 시도합니다. 이는 모듈을 하나의 범위로 합치지만 모든 경우에 합치지는 못합니다. 만약 webapck이 모듈을 합칠 수 없다면 두 가지 대안은 Prevent와 Root 입니다. Prevent는 모듈이 반드시 그 자신의 범위 안에 있어야 한다는 것을 의미합니다. Root는 새로운 모듈 그룹이 생성될 것이라는 의미입니다. 다음의 조건이 결과를 결정합니다.
| Condition | Outcome |
|---|---|
| Non ES6 Module | Prevent |
| Non Import로 가져옴 | Root |
| 다른 청크에서 가져옴 | Root |
| 다수의 다른 모듈 그룹에서 가져옴 | Root |
import()로 가져옴 | Root |
ProvidePlugin의 영향을 받거나 module 사용 | Prevent |
HMR 사용 (module.hot) | Prevent |
eval() 사용 | Prevent |
| 어떤 청크에도 없음 | Prevent |
| import한 쪽과 다른 청크에 있음 | Root |
export * from "cjs-module" | Prevent |
Module Grouping Algorithm
아래의 자바스크립트 의사코드는 이 알고리즘을 설명합니다.
function tryToAdd(group, module) {
if (group.has(module)) {
return true;
}
if (!hasPreconditions(module)) {
return false;
}
const nextGroup = group;
const result = module.dependents.reduce(
(check, dependent) => check && tryToAdd(nextGroup, dependent),
true,
);
if (!result) {
return false;
}
for (const dependency of module.dependencies) {
tryToAdd(group, dependency);
}
group.merge(nextGroup);
return true;
}
for (const module of modules) {
const group = new ModuleGroup({
root: module,
});
for (const dependency of module.dependencies) {
tryToAdd(group, dependency);
}
if (group.modules.length > 1) {
orderedModules = topologicalSort(group.modules);
concatenatedModule = new ConcatenatedModule(orderedModules);
chunk.add(concatenatedModule);
for (const groupModule of orderedModules) {
chunk.remove(groupModule);
}
}
}Debugging Optimization Bailouts
webpack CLI를 사용할 경우 --stats-optimization-bailout 플래그는 bailout 원인을 보여줍니다. webpack config를 사용할 경우 stats 객체에 다음을 추가하세요.
export default {
// ...
stats: {
// Display bailout reasons
optimizationBailout: true,
},
};ModuleFederationPlugin
ModuleFederationPlugin을 사용하면 런타임에 다른 독립 빌드와 함께 모듈을 제공하거나 사용할 수 있습니다.
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
// 여러 옵션을 타입스크립트로 작성합니다.
runtime: string | false,
}),
],
};Options
name
string
이 빌드가 생성하는 컨테이너의 이름입니다. 기본 script 리모트 타입에서는 컨테이너가 할당되는 전역 변수이기도 하며, remoteEntry.js가 로드된 후 소비자가 컨테이너에 접근하는 방법입니다.
페이지의 빌드들 사이에서 고유해야 합니다. 이것은 output.uniqueName과는 별개이며, 그것도 고유해야 합니다 — 모든 빌드에는 고유한 이름이 필요합니다를 참고하세요.
filename
string
output.path에 상대적인 경로로 나타낸 컨테이너 엔트리의 파일 이름입니다. 관례적으로 remoteEntry.js를 사용합니다.
이 빌드가 방출하는 파일의 이름입니다. 소비하는 리모트들의 파일 이름과는 무관합니다 — 그것들은 각 리모트가 선택한 것으로 remotes 아래의 URL에 나타납니다.
remotes
string[] object
이 빌드가 소비하는 컨테이너들입니다. 각 항목은 요청 스코프 — import할 때 사용하는 이름 — 를 컨테이너 위치에 매핑합니다.
new ModuleFederationPlugin({
remotes: {
// request scope container location
app1: "app1@http://localhost:3001/remoteEntry.js",
},
});이후 다음과 같이 import합니다.
import Button from "app1/Button";위치 문자열의 두 부분은 첫 번째 @를 기준으로 분리됩니다.
| 부분 | 의미 |
|---|---|
@ 앞 — app1 | 리모트의 name, 즉 컨테이너가 할당된 전역 변수 |
@ 뒤 — http://localhost:3001/remoteEntry.js | filename이 제공되는 URL |
따라서 설정에 이름이 "두 번" 나타나는 경우, 보통은 우연히 일치하는 두 가지 다른 것입니다. 키는 여러분이 import할 때 사용하는 것으로 자유롭게 변경할 수 있지만, @ 앞 부분은 리모트가 자신에게 붙인 이름과 일치해야 합니다. 둘은 달라도 됩니다.
new ModuleFederationPlugin({
remotes: {
// imported as "checkout/…", served by a container that calls itself "shop"
checkout: "shop@http://localhost:3001/remoteEntry.js",
},
});객체 대신 배열을 전달하면 키 없이 webpack이 각 요청 스코프를 위치에서 추론합니다.
정적으로 설정된 리모트는 직접 아무것도 로드하지 않아도 됩니다. webpack이 <script>를 주입하는 코드를 방출하고 전역 변수가 나타날 때까지 기다리다가, 나타나지 않으면 ScriptExternalLoadError를 보고합니다. 컨테이너를 수동으로 로드하는 것은 빌드 시 위치를 알 수 없는 동적 리모트에서만 필요합니다.
항목은 external(위치)과 shareScope를 가진 객체일 수도 있으며, 하나의 리모트가 나머지와 다른 공유 스코프가 필요한 경우에 사용합니다.
remoteType
string
externalsType으로 리모트를 가져오는 방법입니다.
설정하지 않으면 이 플러그인 자체의 library.type으로 폴백하지만, 그것이 유효한 externals 타입인 경우에만 그렇고, 그렇지 않으면 위에서 설명한 name@url 형식인 'script'로 폴백합니다. library 자체는 기본값이 { type: 'var', name }이므로, 둘 다 설정하지 않은 설정은 'script'를 사용합니다.
runtime
특정한 이름으로 새로운 런타임 청크를 만듭니다.
webpack.config.js
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
runtime: "my-runtime-name",
}),
],
};exposes
이 컨테이너가 다른 빌드에 제공하는 모듈들입니다. 속성 이름은 소비자가 import하는 공개 이름(import Button from "remote/Button")이고, 값은 노출할 모듈입니다.
webpack.config.js
new ModuleFederationPlugin({
name: "remote",
exposes: {
"./Button": "./src/components/Button",
},
});항목은 객체일 수도 있습니다.
import(string): 노출할 모듈입니다.name(string): webpack이 생성하는 청크의 이름입니다. 지정하지 않으면 모듈의 내부 ID를 기반으로 청크 이름이 지정되는데, 빌드가 변경됨에 따라 달라집니다. 설정하면 파일에 정적으로 참조할 수 있는 안정적인 이름을 부여합니다.
new ModuleFederationPlugin({
name: "remote",
exposes: {
"./Button": {
import: "./src/components/Button",
name: "button-chunk",
},
},
});Sharing libraries
설정의 'shared' 키를 사용하면 모듈 연합 사이에 공유되는 라이브러리를 정의할 수 있습니다. 패키지 이름은 package.json의 'dependencies' 섹션에 있는 이름과 동일합니다. 그러나 기본적으로 webpack은 라이브러리의 루트 레벨만 공유합니다.
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
// date-fns를 공유 모듈로 추가합니다.
shared: ["date-fns"],
}),
],
};따라서 응용 프로그램에서 다음과 같은 작업을 수행할 수 있습니다.
import { format } from "date-fns";
format(new Date(2014, 1, 11), "MM/dd/yyyy");그리고 webpack은 date-fns를 공유 라이브러리로 정의하는 모든 모듈 연합 사이에 date-fns를 자동으로 공유합니다.
그러나 패키지의 루트 레벨에 없는 항목(예: date-fns/locale/en-GB/index.js)에 액세스하려면 '공유' 설정에서 패키지 이름에 /를 추가해야 합니다.
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
// date-fns를 공유 모듈로 추가합니다.
// 패키지의 모든 파일이 공유됩니다
shared: ["date-fns/"],
}),
],
};The / 구문을 사용하면 패키지의 모든 파일에 액세스할 수 있습니다. 그러나 특히 개발 모드에서는 성능에 영향을 미치므로 필요한 경우에만 사용해야 합니다.
Specify package versions
공유 라이브러리의 버전을 지정하는 세 가지 방법이 있습니다.
Array syntax
이 구문을 사용하면 패키지 이름만 있는 라이브러리를 공유할 수 있습니다. 이 접근 방식은 프로토타이핑에 적합하지만 react 및 react-dom과 같은 라이브러리에 추가 요구 사항이 필요하기 때문에 대규모 프로덕션 환경으로 확장할 수 없습니다.
import webpack from "webpack";
const { ModuleFederationPlugin } = webpack.container;
export default {
plugins: [
new ModuleFederationPlugin({
// lodash를 공유 모듈로 추가합니다.
// version은 package.json으로부터 추론됩니다.
// requiredVersion도 package.json으로부터 추론됩니다.
// (결정할 수 없으면 경고가 발생합니다)
// 만족하지 않는 공유 버전은 로컬 복사본으로 폴백됩니다.
shared: ["lodash"],
}),
],
};Object syntax
이 구문을 사용하면 패키지 이름을 키로 정의하고 버전(semver)을 값으로 정의할 수 있는 각 공유 라이브러리를 보다 세부적으로 제어할 수 있습니다.
export default {
plugins: [
new ModuleFederationPlugin({
shared: {
// lodash를 공유 모듈로 추가합니다.
// package.json 으로부터 버전을 추론합니다.
// 4.17 이상 5 미만의 버전 중 최상위 버전을 사용하게 됩니다.
lodash: "^4.17.0",
},
}),
],
};Object syntax with sharing hints
이 구문을 사용하면 각 공유 패키지에 힌트를 추가로 제공할 수 있습니다. 패키지의 이름을 키로, 공유 동작을 수정하기 위한 힌트를 포함한 객체를 값으로 정의하여 제공할 수 있습니다.
import { readFileSync } from "node:fs";
const deps = JSON.parse(
readFileSync(new URL("./package.json", import.meta.url)),
).dependencies;
export default {
plugins: [
new ModuleFederationPlugin({
shared: {
// react를 공유 모듈로 추가합니다.
react: {
requiredVersion: deps.react,
singleton: true,
},
},
}),
],
};Sharing hints
eager
boolean
이 힌트를 사용하면 webpack이 비동기식 요청을 통해 라이브러리를 가져오는 대신 제공된 모듈이나 대체 모듈을 직접 포함할 수 있습니다. 즉, 초기 청크에서 이 공유 모듈을 사용할 수 있습니다. 또한 이 힌트가 활성화되면 제공된 모든 모듈과 대체 모듈이 항상 다운로드된다는 점에 주의해야 합니다.
import
false | string
제공된 모듈은 공유 유효범위에 배치되어야 합니다. 이 제공된 모듈은 공유 유효범위에 공유 모듈이 없거나 버전이 유효하지 않은 경우 대체 모듈 역할도 합니다. (이 힌트의 값은 기본적으로 프로퍼티 이름으로 설정됩니다.)
packageName
string
설명 파일에서 필요한 버전을 결정하는 데 사용되는 패키지 이름입니다. ‘packageName’은 요청으로부터 패키지 이름을 자동으로 결정할 수 없는 경우에만 필요합니다.
requiredVersion
false string
이 필드는 필수 패키지 버전을 지정합니다. "^1.2.3"과 같은 시멘틱 버전 관리를 허용합니다. 또한 버전이 URL로 제공되는 경우(예: "git+ssh://git@github.com:foo/bar.git#v1.0.0") 버전을 검색합니다.
webpack은 전체 범위 문법을 구현하므로, 필수 버전은 정확한 버전(1.2.3), 부분 버전(1.2, 모든 패치와 일치), 비교자(>=1.2.3 <2), 캐럿 또는 틸드 범위(^1.2.3, ~1.2.3), 하이픈 범위(1.2.3 - 2.3.4) 또는 이들을 ||로 결합한 것일 수 있습니다. 스코프가 제공하는 모든 것을 허용하려면 false로 설정하세요.
공유 스코프는 동일한 모듈의 여러 버전을 동시에 보유할 수 있습니다 — 각 빌드는 자신이 가진 버전을 제공하고, 소비자는 자신의 requiredVersion을 만족하는 가장 높은 버전을 얻습니다. 이것이 서로 다른 메이저 버전의 라이브러리가 필요한 두 빌드가 한 페이지에서 작동할 수 있는 이유이며, 두 버전 모두 전송하는 비용이 듭니다. singleton: true는 하나의 인스턴스를 유지하기 위해 그 보장을 포기하며, 선택된 버전이 소비자를 만족시키지 않을 때 경고를 발생시킵니다.
shareKey
string
요청된 공유 모듈은 공유 스코프에서 이 키로 조회됩니다. 기본값은 shared에서 사용한 키, 즉 요청 자체이므로 shared: ['lodash']는 lodash로 저장되고 조회됩니다.
빌드가 import하는 이름과 공유할 때 사용해야 하는 이름이 다를 때 설정하세요. 두 빌드는 키와 스코프가 모두 일치할 때만 모듈을 공유합니다. 따라서 lodash-es를 import하는 애플리케이션이 다른 빌드가 lodash로 제공한 것을 재사용할 수 있게 하는 것이 바로 이 키입니다.
new ModuleFederationPlugin({
shared: {
"lodash-es": {
shareKey: "lodash",
},
},
});shareScope
string
공유 스코프의 이름으로, 플러그인 자체의 shareScope 옵션이 기본값이며, 설정되지 않은 경우 'default'입니다. 스코프는 공유 모듈의 하나의 네임스페이스입니다. 빌드는 제공하는 모듈을 스코프에 넣고 필요한 모듈을 스코프에서 조회하므로, 다른 스코프 이름을 사용하는 빌드는 키가 일치하더라도 서로 공유하지 않습니다.
빌드 그룹을 분리하려면 변경하세요 — 예를 들어 페이지가 각자 라이브러리의 복사본을 가져야 하는 두 독립적인 리모트 세트를 호스팅할 때, 또는 위젯이 호스트의 버전을 채택해서는 안 될 때입니다. 제공하는 쪽과 소비하는 쪽 모두 동일한 스코프 이름을 사용해야 합니다.
singleton
boolean
‘singleton’은 공유 범위에서 단일 버전의 공유 모듈만 허용합니다.(기본적으로 비활성화됨) 일부 라이브러리는 전역 내부 상태를 사용합니다.(예: react, react-dom) 따라서 한 번에 하나의 라이브러리 인스턴스만 실행하는 것이 중요합니다.
공유 범위에 동일한 종속성을 갖는 여러 버전이 있는 경우, 가장 높은 시멘틱 버전이 사용됩니다.
strictVersion
boolean
‘strictVersion’을 사용하면 버전이 유효하지 않은 경우 webpack이 공유 모듈을 거부할 수 있습니다(로컬 fallback 모듈을 사용할 수 있고 공유 모듈이 singleton이 아닌 경우 기본값은 true로 설정되고, 그렇지 않으면 false로 설정되며 필수 버전이 지정되지 않은 경우 효과가 없습니다). 필요한 버전이 없으면 런타임 오류가 발생합니다.
version
false | string
제공된 모듈의 버전입니다. Webpack이 일치하는 낮은 버전을 대체할 수 있지만, 더 높은 버전은 대체할 수 없습니다.
Webpack은 기본적으로 디펜던시의 package.json 파일에 있는 버전을 사용합니다.
Additional examples
export default {
plugins: [
new ModuleFederationPlugin({
// vue를 공유 모듈로 추가합니다.
// package.json 으로부터 버전을 추론합니다.
// 항상 공유 버전을 사용하지만 공유된 vue가 2.6.5 미만 또는 3 이상 버전인 경우 경고를 출력합니다.
shared: {
vue: {
requiredVersion: "^2.6.5",
singleton: true,
},
},
}),
],
};export default {
plugins: [
new ModuleFederationPlugin({
// vue를 공유 모듈로 추가합니다.
// 제공된 로컬 버전이 없습니다.
// 공유된 vue가 2.6.5 미만 또는 3 이상 버전이면 경고를 내보냅니다.
shared: {
vue: {
import: false,
requiredVersion: "^2.6.5",
},
},
}),
],
};export default {
plugins: [
new ModuleFederationPlugin({
// vue를 공유 모듈로 추가합니다.
// 제공된 로컬 버전이 없습니다.
// 공유된 vue가 2.6.5 미만 또는 3 이상 버전이면 오류가 발생합니다.
shared: {
vue: {
import: false,
requiredVersion: "^2.6.5",
strictVersion: true,
},
},
}),
],
};export default {
plugins: [
new ModuleFederationPlugin({
shared: {
"my-vue": {
// "my-vue" 가져오기로 참조할 수 있습니다.
import: "vue", // "vue" 패키지는 제공된 대체 모듈로 사용됩니다.
shareKey: "shared-vue", // "shared-vue"이름으로 공유된 모듈은 공유 유효범위에 배치됩니다.
shareScope: "default", // "default"로 공유 유효범위가 적용됩니다.
singleton: true, // 공유 모듈의 단일 버전만 허용됩니다.
strictVersion: true, // 버전이 유효하지 않을 때 공유 버전을 사용하지 말아야 합니다. fallback이 없는 Singleton 또는 모듈은 넘겨집니다. 그렇지 않으면 fallback이 사용됩니다.
version: "1.2.3", // 공유 모듈의 버전입니다.
requiredVersion: "^1.0.0", // 공유 모듈의 최소 버전 입니다.
},
},
}),
],
};NoEmitOnErrorsPlugin
NoEmitOnErrorsPlugin을 사용하면 오류가 있을 때 애셋 방출을 피할 수 있습니다. production 모드에서는 기본적으로 활성화되어 있으며, optimization.emitOnErrors의 기본값이 false입니다. 해당 옵션을 사용하여 활성화하거나 비활성화할 수 있습니다.
webpack.config.js
export default {
plugins: [new webpack.NoEmitOnErrorsPlugin()],
};NormalModuleReplacementPlugin
NormalModuleReplacementPlugin을 사용하면 resourceRegExp와 일치하는 리소스를 newResource로 교체할 수 있습니다. newResource가 상대적인 경우 이전 리소스를 기준으로 해결됩니다. newResource가 함수인 경우 제공된 리소스의 요청 속성을 덮어쓸 것으로 예상됩니다.
이는 빌드 간에 다른 동작을 허용하는 데 유용할 수 있습니다.
new webpack.NormalModuleReplacementPlugin(resourceRegExp, newResource);resourceRegExp는 두 번 테스트됩니다. 해결 전에는 코드에 작성된 요청(예: import sum from './sum'의 './sum')과, 해결 후에는 절대 리소스 경로(예: '/project/src/sum.js')와 대조됩니다.
또한 Windows를 사용하는 경우 폴더 구분 기호가 다르다는 점에 유의하십시오. 예를 들어 /src\/environments\/environment\.ts/는 Windows에서 작동하지 않으며, 대신 /src[\\/]environments[\\/]environment\.ts/를 사용해야 합니다.
Basic Example
개발 환경용으로 빌드할 때 특정 모듈을 교체합니다.
some/path/config.development.module.js 설정 파일과 some/path/config.production.module.js에 프로덕션용 특수 버전이 있다면
프로덕션용으로 빌드할 때 다음 플러그인을 추가합니다.
new webpack.NormalModuleReplacementPlugin(
/some\/path\/config\.development\.js/,
"./config.production.js",
);Advanced Example
지정된 환경에 따른 조건부 빌드에 관한 내용입니다.
다른 빌드 대상에 대한 특정 값이 포함된 설정을 원한다고 가정해 보겠습니다.
export default (env) => {
const appTarget = env.APP_TARGET || "VERSION_A";
return {
plugins: [
new webpack.NormalModuleReplacementPlugin(/-APP_TARGET$/, (resource) => {
resource.request = resource.request.replace(
/-APP_TARGET/,
`-${appTarget}`,
);
if (resource.createData) {
resource.createData.request = resource.request;
}
}),
],
};
};2개의 설정 파일을 생성합니다.
app/config-VERSION_A.js
export default {
title: "I am version A",
};app/config-VERSION_B.js
export default {
title: "I am version B",
};그다음 정규 표현식에서 찾고 있는 키워드를 사용하여 해당 설정을 가져옵니다.
import config from "app/config-APP_TARGET";
console.log(config.title);이제 빌드하려는 대상에 따라 올바른 설정을 가져올 수 있습니다.
npx webpack --env APP_TARGET=VERSION_A
=> 'I am version A'
npx webpack --env APP_TARGET=VERSION_B
=> 'I am version B'PrefetchPlugin
일반 모듈의 요청을 프리페치하면 첫 번째에 import 또는 require가 발생하기 전에 요청을 확인하고 빌드합니다. 플러그인을 사용함으로써 성능을 높일 수 있습니다. 영리한 프리페칭 지점을 결정하기 위해, 먼저 빌드를 프로파일링 하세요.
import webpack from "webpack";
new webpack.PrefetchPlugin([context], request);Options
context: 디렉터리에 대한 절대경로request: 일반 모듈에 대한 요청 문자열
What it does
webpack은 디펜던시 그래프를 순회하면서 모듈을 발견합니다. import 체인 깊은 곳에 있는 모듈은 그 위에 있는 모든 것이 파싱된 후에야 해결됩니다. 따라서 webpack이 가능한 곳에서는 병렬로 모듈을 처리하지만, 긴 체인은 부분적으로 순차적으로 빌드됩니다.
이 플러그인은 대신 컴파일의 맨 처음에 그러한 체인 중 하나를 시작합니다. 그 시점에는 webpack이 첫 번째 모듈들이 파싱되기를 기다리고 있을 것입니다. 실제 import가 발견될 때쯤이면 모듈은 이미 빌드되어 있고 결과가 재사용됩니다.
import path from "node:path";
import webpack from "webpack";
export default {
plugins: [
// 가장 무거운 디펜던시를 즉시 빌드 시작
new webpack.PrefetchPlugin(path.resolve("./src"), "./deeply/nested/heavy"),
],
};빌드에서 한 번도 import하지 않는 모듈을 프리페치하면 작업만 낭비됩니다. 해당 모듈은 어떤 청크에도 포함되지 않고 아무것도 방출되지 않습니다. 따라서 먼저 빌드를 프로파일링하는 것이 좋습니다 — 빌드 성능을 참고하세요. 성과는 전적으로 비용이 많이 들고 늦게 발견되는 요청을 선택하는 데 달려 있습니다.
ProfilingPlugin
플러그인 실행 시간이 포함된 Chrome 프로필 파일을 생성합니다. 기본적으로 events.json 파일을 출력합니다. outputPath옵션을 사용하여 커스텀 파일 경로를 제공할 수 있습니다.
참고: ProfilingPlugin은 절대 경로만 사용합니다.
Options
outputPath: 커스텀 출력 파일(json)의 절대 경로
Usage: default
new webpack.debug.ProfilingPlugin();Usage: custom outputPath
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
new webpack.debug.ProfilingPlugin({
outputPath: path.join(__dirname, "profiling/profileEvents.json"),
});프로필 파일을 보기 위해서는 다음과 같은 과정을 따릅니다.
ProfilingPlugin과 함께 webpack을 실행합니다.- Chrome으로 이동하여 DevTools를 열고
Performance탭(이전 명칭Timeline)으로 이동합니다. - 생성된 파일(기본 값
events.json)을 profiler로 끌어다 놓습니다.
위와 같은 과정을 거치면 plugin당 timeline 통계와 호출들이 표시될 것입니다!
ProgressPlugin
ProgressPlugin은 컴파일 도중에 진행 상황을 보고받을 방법을 자신이 원하는대로 정하는 방법을 제공합니다.
Usage
ProgressPlugin의 인스턴스를 만들고, 허용되는 여러 매개변수 중 하나를 제공하며, 기본 핸들러를 사용자 지정하는 데 사용할 수 있는 정적 메서드 createDefaultHandler도 있습니다.
Providing function
훅이 진행 상황을 보고할 때 호출될 handler 함수를 제공합니다. 함수 handler의 실행 인자:
percentage: 컴파일의 성공 확률을 나타내는 0과 1 사이의 숫자message: 현재 실행중인 훅에 대한 짧은 설명...args: 현재 실행 상황을 묘사하는 0개 이상의 추가적인 문자열
const handler = (percentage, message, ...args) => {
// 예를 들어, 각 진행 메세지를 콘솔에 직접 출력
console.info(percentage, message, ...args);
};
new webpack.ProgressPlugin(handler);Providing object
ProgressPlugin에 object를 제공할 때, 다음의 속성이 지원됩니다
activeModules(boolean = false): 활성화된 모듈의 수와 활성화된 모듈 하나를 진행 메시지로 보여줍니다.entries(boolean = true): 엔트리 수를 진행 메시지로 보여줍니다.handler(Providing function을 보세요)modules(boolean = true): 모듈 수를 진행 메시지로 보여줍니다.modulesCount(number = 5000): 시작할 모듈 수의 최솟값이며modules속성이 활성화되면 적용됩니다.profile(boolean = false): 진행 단계의 프로필 정보를 수집하도록ProgressPlugin에게 알려줍니다.dependencies(boolean = true): 디펜던시 수를 진행 메시지로 보여줍니다.dependenciesCount(number = 10000): 시작할 디펜던시 수의 최솟값이며dependencies속성이 활성화되면 적용됩니다.percentBy('entries' | 'dependencies' | 'modules' | null = null): 진행률을 계산할 방법을ProgressPlugin에게 알려줍니다.progressBar(boolean | 'auto' | object = false) 5.107.0+: 단순한 percentage 메시지 대신 터미널에 progress bar를 렌더링합니다. 기본 handler를 사용할 때만 적용됩니다. 즉 사용자 지정handler를 제공하지 않았을 때만 동작합니다. 기본값으로 활성화하려면true를 전달하고, 대화형 터미널(TTY)에서만 활성화하려면'auto'5.109.0+를 전달하거나, 아래 속성을 가진 object를 전달할 수 있습니다.name(string = 'Build'): progress bar 앞에 표시되는 이름입니다.color(string = 'green'): bar가 채워진 부분에 사용할 색상입니다. 지정한 색상을 지원하지 않으면'green'을 사용합니다.width(number = 25) 5.109.0+: progress bar의 문자 단위 너비입니다.
estimatedTime(boolean = false) 5.109.0+: build 진행률을 기준으로 남은 예상 시간을 보여줍니다.phaseTimings(boolean = false) 5.109.0+: build가 완료되면 각 build 단계의 timing breakdown을 보여줍니다.
new webpack.ProgressPlugin({
activeModules: false,
entries: true,
handler(percentage, message, ...args) {
// 사용자 지정 로직
},
modules: true,
modulesCount: 5000,
profile: false,
dependencies: true,
dependenciesCount: 10000,
percentBy: null,
});progress bar 렌더링:
new webpack.ProgressPlugin({
progressBar: {
name: "Build",
color: "green",
},
});webpack.ProgressPlugin.createDefaultHandler
ProgressPlugin의 기본 핸들러가 요구 사항을 충족하지 않는 경우 정적 ProgressPlugin.createDefaultHandler 메서드를 사용하여 사용자 지정할 수 있습니다.
static createDefaultHandler: (
profile: undefined | null | boolean,
logger: WebpackLogger,
options?: {
progressBar?: { name: string; color: string; width: number } | false;
estimatedTime?: boolean;
phaseTimings?: boolean;
},
) => (percentage: number, msg: string, ...args: string[]) => void;Percentage calculation
기본적으로 진행률은 빌드된 모듈 수 대 전체 모듈 수, 완료된 엔트리 수 대 전체 엔트리 수, 처리된 디펜던시 수 대 전체 디펜던시 수, 세 비율의 중앙값입니다. percentBy를 설정하면 이 비율 중 하나를 대신 사용합니다.
총 모듈 수는 사전에는 알 수 없고 빌드를 거치면서 변합니다. 이는 부정확한 진행률을 야기할 수 있습니다.
이러한 문제를 해결하기 위해 ProgressPlugin은 마지막으로 알려진 총 모듈 수를 캐시하여 이 값을 다음 빌드 때 재사용합니다. 첫 번째 빌드는 데이터 값을 메모리에 캐시하지만 그 다음 빌드들은 이 값을 사용하고 업데이트합니다.
다중 구성된 엔트리 포인트가 있는 프로젝트들을 위한 설정에는
percentBy: 'entries'를 사용할 것을 추천합니다. 엔트리 포인트의 개수를 미리 알 수 있기 때문에 확률 계산은 더욱 정확해질 것입니다.
Supported Hooks
다음 훅은 ProgressPlugin에 진행 정보를 보고합니다.
Compiler
- beforeRun*
- run*
- watchRun*
- normalModuleFactory*
- contextModuleFactory*
- beforeCompile*
- compile*
- thisCompilation*
- compilation*
- make
- finishMake*
- emit*
- afterEmit*
- done*
- watchClose*
Compiler cache (compiler.cache.hooks)
- endIdle*
- storeBuildDependencies*
- shutdown*
- beginIdle*
Compilation
- buildModule (only when
activeModulesis enabled) - addEntry
- failedEntry
- succeedEntry
- finishModules*
- seal*
- optimizeDependencies*
- afterOptimizeDependencies*
- beforeChunks*
- afterChunks*
- optimize*
- optimizeModules*
- afterOptimizeModules*
- optimizeChunks*
- afterOptimizeChunks*
- optimizeTree*
- afterOptimizeTree*
- optimizeChunkModules*
- afterOptimizeChunkModules*
- reviveModules*
- beforeModuleIds*
- moduleIds*
- optimizeModuleIds*
- afterOptimizeModuleIds*
- reviveChunks*
- beforeChunkIds*
- chunkIds*
- optimizeChunkIds*
- afterOptimizeChunkIds*
- recordModules*
- recordChunks*
- beforeModuleHash*
- beforeCodeGeneration*
- beforeRuntimeRequirements*
- beforeHash*
- afterHash*
- recordHash*
- beforeModuleAssets*
- beforeChunkAssets*
- processAssets*
- afterProcessAssets*
- record*
- afterSeal*
Source
ProvidePlugin
모듈을 import 또는 require 할 필요 없이 자동으로 로드합니다.
new webpack.ProvidePlugin({
identifier: "module1",
// ...
});또는
new webpack.ProvidePlugin({
identifier: ["module1", "property1"],
// ...
});기본적으로, 모듈 해석 경로는 현재 폴더 (./**)와 node_modules입니다.
또한 전체 경로도 지정할 수 있습니다.
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
new webpack.ProvidePlugin({
identifier: path.resolve(path.join(__dirname, "src/module1")),
// ...
});모듈에서 자유 변수로 식별자를 만날 때마다 모듈이 자동으로 로드되고, 식별자가 로드된 모듈의 exports(혹은 지정된 exports를 돕기 위한 프로퍼티)로 채워집니다.
ES2015 모듈의 기본 export를 가져오려면 모듈의 기본 프로퍼티를 지정해야 합니다.
Usage: jQuery
jquery를 자동으로 로드하려면 해당 노드 모듈에 노출되는 두 변수를 모두 가리킬 수 있습니다.
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
});소스 코드 중 어느 것이라도 동일합니다.
// 모듈에서
$("#item"); // <= 동작합니다.
jQuery("#item"); // <= 이 또한 동작합니다.
// $는 모듈 "jquery" exports로 자동 설정되었습니다.Usage: jQuery with Angular 1
Angular는 jQuery가 존재하는지 찾기 위해 window.jQuery를 찾습니다. 소스 코드를 참고하세요.(https://github.com/angular/angular.js/blob/v1.5.9/src/Angular.js#L1821-L1823).
new webpack.ProvidePlugin({
"window.jQuery": "jquery",
});Usage: Lodash Map
new webpack.ProvidePlugin({
_map: ["lodash", "map"],
});Usage: Vue.js
new webpack.ProvidePlugin({
Vue: ["vue/dist/vue.esm.js", "default"],
});SourceMapDevToolPlugin
이 플러그인을 사용하면 소스맵 생성을 더욱더 세밀하게 제어 할 수 있습니다. 이 플러그인은 devtool의 설정 옵션을 통해 자동으로 활성화됩니다.
new webpack.SourceMapDevToolPlugin(options);Options
다음과 같은 옵션이 제공됩니다.
type Matcher =
| string
| RegExp
| ((name: string) => boolean)
| (string | RegExp | ((name: string) => boolean))[];string은 접두사로 일치하고, RegExp는 테스트로 일치하며, 함수는 이름과 함께 호출되어 일치 여부를 반환합니다.
-
test(Matcher): 확장자에 따라 모듈의 소스맵을 포함합니다(기본값은.js,.cjs,.mjs및.css). -
include(Matcher): 주어진 값과 일치하는 모듈 경로에 대한 소스맵을 포함합니다. -
exclude(Matcher): 소스맵 생성에서 주어진 값과 일치하는 모듈을 제외합니다. -
filename(string): 소스맵의 출력 파일 이름을 정의합니다(값이 제공되지 않으면 인라인으로 설정됩니다). -
append(string(pathData: PathData, assetInfo?: AssetInfo) => stringfalse): 기존 애셋에 값을 추가합니다. 일반적으로#sourceMappingURL주석이 사용됩니다.[url]은 소스맵 파일의 URL로 대체됩니다. webpack v4.36.0부터는[chunk],[filename]및[contenthash]와 같은 경로 파라미터가 지원됩니다.append를false로 설정하면 추가가 비활성화됩니다.버전 5.84.0부터 webpack은 경로와 애셋 정보 객체를 인수로 받아들이고 문자열을 반환하는 'append' 옵션을 허용합니다.
(pathData: PathData, assetInfo?: AssetInfo) => string; -
moduleFilenameTemplate(string):output.devtoolModuleFilenameTemplate을 참고하세요. -
fallbackModuleFilenameTemplate(string): 위 링크를 참고하세요. -
ignoreList(Matcher): 소스맵에서 지정된 값과 일치하는 소스 파일을 무시할지 여부를 결정합니다. -
module = true(boolean): 로더가 소스맵을 생성해야 하는지 여부를 나타냅니다. -
columns = true(boolean): 열 매핑을 사용해야 하는지 여부를 나타냅니다. -
namespace(string): DevTools에서 여러 webpack 루트를 허용하는 네임스페이스 접두사입니다.output.devtoolNamespace를 참고하세요. -
noSources = false(boolean): 소스 파일 콘텐츠가 소스맵에 포함되지 않도록 합니다. -
publicPath(string): 공개 경로 접두사가 있는 절대 URL을 생성합니다(예:https://example.com/project/). -
fileContext(string):[file]인수를 이 디렉터리를 기준으로 합니다. -
sourceRoot(string): 소스맵의sourceRoot속성에 사용자 지정 값을 제공합니다. -
debugIds(boolean):true로 설정하면 소스 및 소스맵에서 고유 ID가 생성되어 여러 빌드에서 소스맵을 식별하는 데 도움이 됩니다. 자세한 내용은 TC39 소스맵 디버그 ID 제안을 참고하세요.
fileContext 옵션은 ../../가 절대 [url]에 나타나지 않도록 상위 디렉터리에 소스맵을 저장하려는 경우에 유용합니다.
Examples
다음은 이 플러그인의 몇 가지 일반적인 사용 사례입니다.
Basic Use Case
다음 코드를 사용하여 설정 옵션 devtool: inline-source-map을 커스텀 플러그인 설정으로 동등하게 바꿀 수 있습니다.
export default {
// ...
devtool: false,
plugins: [new webpack.SourceMapDevToolPlugin({})],
};Exclude Vendor Maps
다음 코드는 vendor.js 번들 내 모듈의 소스맵을 제외합니다.
new webpack.SourceMapDevToolPlugin({
filename: "[file].map[query]",
exclude: ["vendor.js"],
});Host Source Maps Externally
소스맵의 URL을 설정하세요. 인증이 필요한 호스트에 호스팅하는 데 유용합니다.
new webpack.SourceMapDevToolPlugin({
append: "\n//# sourceMappingURL=https://example.com/sourcemap/[url]",
filename: "[file].map[query]",
});소스맵이 상위 디렉터리에 저장되는 경우,
project
|- dist
|- public
|- bundle-[hash].js
|- sourcemaps
|- bundle-[hash].js.map다음과 같이 설정하면
new webpack.SourceMapDevToolPlugin({
filename: "sourcemaps/[file].map",
publicPath: "https://example.com/project/",
fileContext: "public",
});다음 URL을 생성합니다.
https://example.com/project/sourcemaps/bundle-[hash].js.mapSplitChunksPlugin
원래 청크(및 그 안에 가져온 모듈)는 webpack 내부 그래프에서 부모-자식 관계로 연결되었습니다. CommonsChunkPlugin은 중복되는 의존성을 피하고자 사용되었지만, 추가 최적화는 불가능했습니다.
webpack v4부터 optimization.splitChunks를 위해 CommonsChunkPlugin은 제거되었습니다.
Defaults
즉시 사용 가능한 SplitChunksPlugin은 대부분의 사용자에게 잘 작동합니다.
초기 청크를 변경하면 HTML 파일이 프로젝트를 실행하기 위해 포함해야 하는 스크립트 태그에 영향을 미치기 때문에 기본적으로 on-demand 청크에만 영향을 미칩니다.
Webpack은 다음 조건에 따라 자동으로 청크를 분할합니다.
- 새 청크를 공유할 수 있거나 모듈이
node_modules폴더에 있는 경우 - 새 청크가 20kb보다 큰 경우(min+gz 이전에;
production모드 이외에서는 10kb) - 요청 시 청크를 로드할 때 최대 병렬 요청 수가 30개 이하일 경우(
production모드 이외에서는 무제한) - 초기 페이지 로드 시 최대 병렬 요청 수가 30개 이하일 경우(
production모드 이외에서는 무제한)
마지막 두 가지 조건을 충족하려고 할 때 더 큰 청크가 선호됩니다.
Configuration
Webpack은 이 기능에 대한 더 많은 제어를 원하는 개발자를 위해 옵션 세트를 제공합니다.
optimization.splitChunks
이 설정 객체는 production mode에서 SplitChunksPlugin의 기본 동작을 나타냅니다. 주석은 다른 모드에서 사용되는 값을 나타냅니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
chunks: "async",
minSize: 20000, // 10000 in other modes
minRemainingSize: 20000, // equals minSize; 0 in development mode
minChunks: 1,
maxAsyncRequests: 30, // Infinity in other modes
maxInitialRequests: 30, // Infinity in other modes
enforceSizeThreshold: 50000, // 30000 in other modes
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};splitChunks.automaticNameDelimiter
string = '-'
기본적으로 webpack은 출처와 청크 이름을 사용하여 이름을 생성합니다(예: vendors-main.js). 이 옵션을 사용하면 생성된 이름에 사용할 구분 기호를 지정할 수 있습니다.
splitChunks.chunks
'initial' | 'async' | 'all' = 'async' RegExp (chunk: Chunk) => boolean | undefined
최적화를 위해 선택될 청크를 나타냅니다. 문자열이 제공될 때 유효한 값은 all, async 및 initial입니다.
'initial'은 애플리케이션 코드가 실행되기 전에 브라우저가 미리 로드하는 청크, 즉 엔트리 포인트에 속하는 청크(런타임 및 정적으로 import하는 모든 것 포함)를 선택합니다.'async'는 온디맨드로 로드되는 청크, 즉import()와 다른 비동기 디펜던시로 생성된 청크를 선택합니다.'all'은 둘 다 선택합니다.
청크는 속한 청크 그룹 중 하나 이상이 페이지와 함께 로드될 때 initial로 간주됩니다. 따라서 엔트리 포인트의 일부이면서 다른 곳에서 import()로 가져오는 청크는 async가 아닌 initial로 처리됩니다. all을 제공하는 것은 비동기 청크와 비비동기 청크 간에도 청크를 공유할 수 있다는 것을 의미하기 때문에 특히 강력할 수 있습니다.
대체 캐시 그룹(splitChunks.fallbackCacheGroup.chunks)에도 적용됩니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
// 모든 유형의 청크를 포함합니다.
chunks: "all",
},
},
};또는 더 많은 제어를 위한 기능을 제공할 수 있습니다. 반환 값은 각 청크를 포함할지 여부를 나타냅니다.
export default {
// ...
optimization: {
splitChunks: {
chunks(chunk) {
// `my-excluded-chunk`를 제외합니다.
return chunk.name !== "my-excluded-chunk";
},
},
},
};Webpack 버전 5.86.0 이상을 사용하는 경우 정규식을 전달할 수도 있습니다.
export default {
// ...
optimization: {
splitChunks: {
chunks: /foo/,
},
},
};splitChunks.maxAsyncRequests
number
on-demand 로드 시의 최대 병렬 요청 수입니다.
splitChunks.maxAsyncRequests의 기본값은 mode에 따라 달라집니다.
| 모드 | 기본값 |
|---|---|
"production" | 30 |
"development" | Infinity |
"none" | Infinity |
splitChunks.maxInitialRequests
number
엔트리 포인트의 최대 병렬 요청 수입니다.
splitChunks.maxInitialRequests의 기본값은 mode에 따라 달라집니다.
| 모드 | 기본값 |
|---|---|
"production" | 30 |
"development" | Infinity |
"none" | Infinity |
splitChunks.defaultSizeTypes
string[] = ['javascript', 'css', 'unknown']
크기에 숫자를 사용할 때 적용되는 크기 유형을 설정합니다. 내장 CSS 지원(experiments.css)이 비활성화되면 기본값은 ['javascript', 'unknown']입니다.
splitChunks.minChunks
number = 1
모듈이 분할 전에 청크 간에 공유되어야 하는 최소 개수입니다.
splitChunks.hidePathInfo
boolean
maxSize로 분할된 부분의 이름을 만들 때 경로 정보가 노출되지 않도록 합니다.
splitChunks.minSize
number Record<string, number>
생성할 청크의 최소 byte 크기입니다.
splitChunks.minSize의 기본값은 mode에 따라 달라집니다.
| 모드 | 기본값 |
|---|---|
"production" | 20000 |
"development" | 10000 |
"none" | 10000 |
splitChunks.minSizeReduction
number Record<string, number>
청크를 생성하는 데 필요한 기본 청크(번들)의 바이트 단위의 줄어들 최소 크기입니다. 즉, 청크로 분할해도 기본 청크(번들)의 크기가 주어진 바이트만큼 줄어들지 않으면 splitChunks.minSize 값을 충족하더라도 분할되지 않습니다.
splitChunks.enforceSizeThreshold
splitChunks.cacheGroups.{cacheGroup}.enforceSizeThreshold
number
분할이 적용되고 기타 제한(minRemainingSize, maxAsyncRequests, maxInitialRequests)이 무시되는 임계 크기 값입니다.
splitChunks.enforceSizeThreshold의 기본값은 mode에 따라 달라집니다.
| 모드 | 기본값 |
|---|---|
"production" | 50000 |
"development" | 30000 |
"none" | 30000 |
splitChunks.minRemainingSize
splitChunks.cacheGroups.{cacheGroup}.minRemainingSize
number
splitChunks.minRemainingSize 옵션은 webpack 5에서 도입되었으며, 분할 후 남는 청크의 최소 크기가 특정 제한 이상인지 확인하여 크기가 0인 모듈이 생성되는 것을 방지합니다.
splitChunks.minRemainingSize의 기본값은 mode에 따라 달라집니다.
| 모드 | 기본값 |
|---|---|
"production" | splitChunks.minSize의 값 |
"development" | 0 |
"none" | splitChunks.minSize의 값 |
세밀한 제어가 필요한 드문 경우를 제외하면 직접 지정할 필요가 없습니다.
splitChunks.maxSize
number = 0
maxSize를 사용하면(캐시 그룹 optimization.splitChunks.cacheGroups[x].maxSize당 전역적으로 optimization.splitChunks.maxSize 또는 대체 캐시 그룹 optimization.splitChunks.fallbackCacheGroup.maxSize의 경우) webpack이 maxSize byte보다 큰 청크를 더 작은 부분으로 분할하도록 합니다. 분할된 크기는 최소 minSize(maxSize 다음)입니다.
알고리즘은 결정론적이며 모듈 변경은 로컬에만 영향을 미칩니다. 따라서 장기 캐싱을 사용할 때 사용할 수 있으며 기록이 필요하지 않습니다. maxSize는 힌트일 뿐이며 모듈이 maxSize 보다 크거나 분할이 minSize를 벗어날 때 위반될 수 있습니다.
청크에 이미 이름이 있는 경우 각 부분은 해당 이름에서 파생된 새 이름을 얻습니다. optimization.splitChunks.hidePathInfo의 값에 따라 첫 번째 모듈 이름이나 해시에서 파생된 키를 추가합니다.
maxSize 옵션은 HTTP/2 및 장기 캐싱과 함께 사용하기 위한 것입니다. 더 나은 캐싱을 위해 요청수가 증가합니다. 빠른 재구축을 위해 파일 크기를 줄이는 데도 사용할 수 있습니다.
splitChunks.maxAsyncSize
number
maxSize와 마찬가지로 maxAsyncSize는 전역적으로(splitChunks.maxAsyncSize) 캐시 그룹(splitChunks.cacheGroups.{cacheGroup}.maxAsyncSize) 또는 대체 캐시 그룹(splitChunks.fallbackCacheGroup.maxAsyncSize)에 적용될 수 있습니다.
maxAsyncSize와 maxSize의 차이점은 maxAsyncSize가 on-demand 로딩 청크에만 영향을 미친다는 점입니다.
splitChunks.maxInitialSize
number
maxSize와 마찬가지로 maxInitialSize는 전역적으로(splitChunks.maxInitialSize) 캐시 그룹(splitChunks.cacheGroups.{cacheGroup}.maxInitialSize) 또는 대체 캐시 그룹(splitChunks.fallbackCacheGroup.maxInitialSize)에 적용될 수 있습니다.
maxInitialSize와 maxSize의 차이점은 maxInitialSize가 초기 로딩 청크 에만 영향을 미친다는 것입니다.
splitChunks.name
false = false string (module: Module, chunks: Chunk[], cacheGroupKey: string) => string | undefined
또한 splitChunks.cacheGroups.{cacheGroup}.name와 같이 각 캐시 그룹에 대해서도 사용 가능합니다.
이는 분할 청크의 이름입니다. false를 제공하면 청크의 이름이 동일하게 유지되므로 불필요하게 이름이 변경되지 않습니다. 프로덕션 빌드에 권장되는 값입니다.
문자열이나 함수를 제공하면 이름을 커스텀 할 수 있습니다. 항상 같은 문자열을 반환하는 문자열이나 함수를 지정하면 모든 공통 모듈과 vendor가 단일 청크로 병합됩니다. 이로 인해 초기 다운로드가 더 커지고 페이지 로드가 느려질 수 있습니다.
함수를 명시한 경우 청크의 이름을 선택하는 데 특히 유용한 chunk.name 속성(여기서 chunk는 chunks 배열의 요소)을 찾을 수 있습니다.
splitChunks.name이 기존 청크의 이름(예: 엔트리 포인트)과 일치하는 경우, 선택된 모듈은 해당 청크가 모든 선택된 청크의 부모일 때만(예: dependOn을 통해) 해당 청크로 이동됩니다. 그렇지 않으면 webpack이 오류를 발생시킵니다.
main.js
import _ from "lodash";
console.log(_.join(["Hello", "webpack"], " "));webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
// 여기서 cacheGroupKey는 cacheGroup의 키로 `commons`입니다.
name(module, chunks, cacheGroupKey) {
const moduleFileName = module
.identifier()
.split("/")
.reduceRight((item) => item);
const allChunksNames = chunks.map((item) => item.name).join("~");
return `${cacheGroupKey}-${allChunksNames}-${moduleFileName}`;
},
chunks: "all",
},
},
},
},
};splitChunks 구성으로 webpack을 실행하면 다음의 이름으로 공통 그룹 청크도 출력됩니다. commons-main-lodash.js.e7519d2bb8777058fa27.js(해시는 실제 출력의 예로 제공됩니다).
splitChunks.usedExports
splitChunks.cacheGroups{cacheGroup}.usedExports
boolean
모듈이 export할 파일의 이름을 수정(mangle)하고 사용하지 않는 export를 생략하고 보다 효율적인 코드를 생성하기 위해 어떤 export를 사용하는지 알아봅니다.
optimization.usedExports가 true일 때(프로덕션 모드의 경우) 기본값은 true이고, 그렇지 않으면 false입니다.
splitChunks.fallbackCacheGroup
object
어떤 캐시 그룹도 선택하지 않은 모듈에 대한 옵션으로, maxSize를 준수하기 위해 청크를 추가로 분할해야 할 때 webpack이 사용하는 그룹입니다. chunks, minSize, maxSize, maxAsyncSize, maxInitialSize 및 automaticNameDelimiter를 허용하며, 각각 splitChunks 수준에서 설정된 값을 기본값으로 사용합니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
fallbackCacheGroup: {
maxSize: 100000,
automaticNameDelimiter: "~",
},
},
},
};splitChunks.cacheGroups
캐시 그룹은 splitChunks.*의 모든 옵션을 상속 및(또는) 재정의할 수 있습니다. 그러나 test, priority 및 reuseExistingChunk는 캐시 그룹 수준에서만 구성할 수 있습니다. 기본 캐시 그룹을 비활성화하려면 false로 설정하세요.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
default: false,
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.priority
number = -20
모듈은 여러 캐시 그룹에 속할 수 있습니다. 최적화는 priority(우선순위)가 더 높은 캐시 그룹을 선호합니다. 기본 그룹은 커스텀 그룹이 더 높은 우선순위를 가질 수 있도록 음수 우선순위를 갖습니다(커스텀 그룹일 경우 기본값은 0입니다).
splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk
boolean = false
현재 청크에 이미 기본 번들에서 분리된 모듈이 포함되어 있으면 새로 생성되는 대신 재사용됩니다. 이것은 청크의 파일 이름에 영향을 줄 수 있습니다. 내장된 default 및 defaultVendors 캐시 그룹은 이 값을 true로 설정합니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
reuseExistingChunk: true,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.type
function RegExp string
모듈 유형별로 캐시 그룹에 모듈을 할당할 수 있습니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
json: {
type: "json",
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.layer
RegExp string function
모듈 계층별로 캐시 그룹에 모듈을 할당합니다.
splitChunks.cacheGroups.test
splitChunks.cacheGroups.{cacheGroup}.test
RegExp string (module: Module, context: { chunkGraph: ChunkGraph, moduleGraph: ModuleGraph }) => boolean
캐시 그룹에 의해 선택되는 모듈을 제어합니다. 생략하면 모든 모듈이 선택됩니다. 모듈의 리소스 경로(module.nameForCondition())와 대조됩니다. 문자열은 접두사로 일치하고, RegExp는 테스트로 일치합니다.
아래와 같이 {cacheGroup}.test에 기능을 제공합니다.
webpack.config.js
import path from "node:path";
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
svgGroup: {
test(module) {
// `module.resource`는 디스크에 있는 파일의 절대 경로를 포함합니다.
// 플랫폼 간 호환성을 위해 / 또는 \ 대신 `path.sep`을 사용합니다.
return (
module.resource &&
module.resource.endsWith(".svg") &&
module.resource.includes(`${path.sep}cacheable_svgs${path.sep}`)
);
},
},
byModuleTypeGroup: {
test(module) {
return module.type === "javascript/auto";
},
},
},
},
},
};module 및 chunks 객체에서 어떤 정보를 사용할 수 있는지 확인하려면 콜백에 debugger; 문을 넣으면 됩니다. 그런 다음 디버그 모드에서 webpack 빌드를 실행하여 Chromium DevTools의 파라미터를 검사합니다.
아래는 {cacheGroup}.test에 RegExp를 제공한 경우입니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
// 플랫폼 간 호환성을 위한 경로 구분 기호로 `[\\/]`의 사용에 유의하세요.
test: /[\\/]node_modules[\\/]|vendor[\\/]analytics_provider|vendor[\\/]other_lib/,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.filename
string (pathData: PathData, assetInfo?: AssetInfo) => string
이 캐시 그룹이 생성하는 청크(initial 및 on-demand 모두)의 파일 이름 템플릿을 재정의합니다. splitChunks.filename으로 전역적으로 설정할 수도 있습니다.
output.filename에서 사용 가능한 모든 플레이스홀더는 여기에서도 사용할 수 있습니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: "[name].bundle.js",
},
},
},
},
};아래는 함수로 사용하는 방법입니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: (pathData) =>
// 요구 사항에 따라 파일 이름 문자열을 생성하기 위해 pathData 객체를 사용하세요.
`${pathData.chunk.name}-bundle.js`,
},
},
},
},
};파일 이름 앞에 경로를 제공하여 폴더 구조를 생성할 수 있습니다(예: 'js/vendor/bundle.js').
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: "js/[name]/bundle.js",
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.enforce
boolean = false
Webpack에 splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequests 및 splitChunks.maxInitialRequests 옵션을 무시하고 항상 이 캐시 그룹에 대한 청크를 생성하도록 지시합니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
enforce: true,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.idHint
string
청크 ID에 대한 힌트를 설정합니다. 청크의 파일 이름에 추가됩니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
idHint: "vendors",
},
},
},
},
};Examples
Defaults: Example 1
// index.js
import("./a"); // 동적 import// a.js
import "react";
// ...결과: react를 포함하는 별도의 청크가 생성됩니다. import 호출에서 이 청크는 ./a를 포함하는 원래 청크와 병렬로 로드됩니다.
이유:
- 조건 1: 청크는
node_modules의 모듈을 포함합니다 - 조건 2:
react가 20kb보다 큽니다 - 조건 3: import 호출 시 병렬 요청 수는 2입니다
- 조건 4: 초기 페이지 로드 시 요청에 영향을 주지 않습니다
이 이유는 무엇일까요? react는 애플리케이션 코드만큼 자주 변경되지 않을 것입니다. 별도의 청크로 이동하면 이 청크를 앱 코드와 별도로 캐시할 수 있습니다(청크 해시, 레코드, Cache-Control 또는 장기 캐시 접근 방식을 사용한다고 가정합니다).
Defaults: Example 2
// entry.js
// 동적 imports
import("./a");
import("./b");// a.js
import "./helpers"; // helpers의 크기는 40kb입니다
// ...// b.js
import "./helpers";
import "./more-helpers"; // more-helpers 또한 40kb의 크기를 가집니다
// ...결과: ./helpers와 이에 대한 모든 의존성을 포함하는 별도의 청크가 생성됩니다. import 호출에서 이 청크는 원래 청크와 병렬로 로드됩니다.
이유:
- 조건 1: 청크는 두 import 호출 간에 공유됩니다
- 조건 2:
helpers가 20kb보다 큽니다 - 조건 3: import 호출 시 병렬 요청 수는 2입니다
- 조건 4: 초기 페이지 로드 시 요청에 영향을 주지 않습니다
helpers의 내용을 각 청크에 넣으면 코드가 두 번 다운로드됩니다. 별도의 청크를 사용하면 한 번만 발생합니다. 우리는 추가 요청 비용을 지불하며 이는 절충안으로 간주할 수 있습니다. 그렇기 때문에 최소 크기는 20kb입니다.
Split Chunks: Example 1
엔트리 포인트 간에 공유되는 모든 코드를 포함하는 commons 청크를 만듭니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "commons",
chunks: "initial",
minChunks: 2,
},
},
},
},
};Split Chunks: Example 2
전체 애플리케이션에서 node_modules의 모든 코드를 포함하는 vendors 청크를 만듭니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
},
},
},
},
};Split Chunks: Example 3
RegExp와 일치하는 특정 node_modules 패키지를 포함하는 custom vendor 청크를 만듭니다.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: "vendor",
chunks: "all",
},
},
},
},
};SvgChunkWebpackPlugin
The svg-chunk-webpack-plugin creates optimized SVG sprites, according to Webpack's entrypoints. Each sprite contains only the SVG dependencies listed on its entrypoints to improved code splitting, even on SVG files.
The plugin includes the popular SVGO package to generates clean and optimized SVG sprites.
Code splitting is the key to deliver files without any content that is unused by the pages. It already exists for CSS, Javascript and now for SVG files with this plugin.
When to use this plugin
On multiple page application, each pages must includes only its necessary dependencies. In other words, it must include only the SVG files imported by its entrypoint and all its dependencies.
With reusable components, SVGs are often duplicated on all the project. Now, you can create a global SVG library and every Javascript files can easily import any SVG from this library. Entrypoint dependencies are automatically updated, thanks to the Webpack compilation.
When you work with SVGs exported by design softwares, like Sketch or Illustrator, their source code is never optimized and often contains comments, CSS classes which can create conflicts between them. The plugin automatically cleans all SVGs before creating the sprite.
Zero config
The plugin works without configuration with already the optimized settings. For advanced usage, see the section using configuration.
Installation
The plugin is available as a package with the name of svg-chunk-webpack-plugin on npm and Github.
npm install svg-chunk-webpack-plugin --save-devyarn add svg-chunk-webpack-plugin --dev[!WARNING] Plugin
svg-chunk-webpack-plugin@5is ESM only.\n
[!NOTE] Minimum supported
Node.jsversion is16.20.0and Webpack>=5.10.3.
[!NOTE] The plugin is compatible with html-webpack-plugin (optional dependency). Use the
injectSpritesInTemplatesoption to automatically inject sprites into your HTML templates.
Example
The project includes a minimalist example in the ./example directory. Run the npm run build:example command to execute the Webpack example and see the plugin's implementation in action.
Basic usage
The plugin will generate one SVG sprite for each entrypoints. Sprites are built in the output path directory with all the other assets. Each sprite filename is composed with its entrypoint name (in the example below, that would be home.svg).
First, let's add the loader and the plugin to the Webpack configuration.
[!WARNING] The loader and the plugin need to works together.
webpack.config.js
import SvgChunkWebpackPlugin from 'svg-chunk-webpack-plugin';
export default {
module: {
rules: [
{
test: /\.svg$/,
use: [
{
loader: SvgChunkWebpackPlugin.loader
}
]
}
]
},
plugins: [new SvgChunkWebpackPlugin()]
};[!NOTE] For more flexibility and better performance, inline SVG files are better. Fewer HTTP requests, CSS properties to change the style, no flickering during the page load.
Then, include the sprite in the wanted pages (we use Twig in the following example).
home.html.twig
{{ include 'home.svg' }}Finally, use the SVG with the <use> tag, like the following example. Replace <svg_name> by the SVG name (without the extension).
home.html.twig
<svg>
<use href="#<svg_name>"></use>
</svg>Using a configuration
The loader and the plugin accepts configuration to override the default behavior.
Loader
The loader configuration allow to personalize the SVGO configuration. SVGO optimization is executed during the loader process to optimize build performance.
configFile
Type:
type configFile = string | boolean;Default: path.resolve(opts.root, 'svgo.config.js')
Tells the loader whether to load the custom SVGO configuration. Custom configuration can be disabled with configFile: false.
webpack.config.js
export default {
module: {
rules: [
{
test: /\.svg$/,
loader: SvgChunkWebpackPlugin.loader,
options: {
configFile: './path/svgo.config.js'
}
}
]
}
};SVGO custom configuration
SVGO have a default preset to optimize SVG files. See how to configure svgo for details.
svgo.config.js
export default {
multipass: true,
plugins: [
{
name: 'preset-default',
params: {
overrides: {
inlineStyles: {
onlyMatchedOnce: false
},
removeViewBox: false
}
}
},
{
name: 'convertStyleToAttrs'
}
]
};Plugin
The plugin configuration allow to personalize sprite settings.
filename
Type:
type filename = string;Default: '[name].svg'
Tells the plugin whether to personalize the default sprite filename. The placeholder [name] is automatically replaced by entrypoints names.
webpack.config.js
export default {
plugins: [
new SvgChunkWebpackPlugin({
filename: '[name].svg'
})
]
};[!NOTE] The
filenameparameter is compatible with Webpack caching placeholders, see the section caching.
svgstoreConfig
Type:
type svgstoreConfig = object;Default: { cleanDefs: false, cleanSymbols: false, inline: true }
SVG sprites are built using the svgstore package. Update the parameters according to your needs from the options list available on the svgstore documentation.
webpack.config.js
export default {
plugins: [
new SvgChunkWebpackPlugin({
svgstoreConfig: {
svgAttrs: {
'aria-hidden': true,
style: 'position: absolute; width: 0; height: 0; overflow: hidden;'
}
}
})
]
};[!NOTE] To avoid LinearGradient conflicts, avoid the
display: noneproperty which breaks SVG definitions.
generateSpritesManifest
Type:
type generateSpritesManifest = boolean;Default: false
Tells the plugin whether to generate the sprites-manifest.json. The JSON file contains the list of all SVG included by entrypoints.
webpack.config.js
export default {
plugins: [
new SvgChunkWebpackPlugin({
generateSpritesManifest: true
})
]
};generateSpritesPreview
Type:
type generateSpritesPreview = boolean;Default: false
Tells the plugin whether to generate the sprites-preview.html. The HTML preview contains a display list of all SVG included by entrypoints with the SVG overviews and the names. See the sprites preview of the example.
webpack.config.js
export default {
plugins: [
new SvgChunkWebpackPlugin({
generateSpritesPreview: true
})
]
};injectSpritesInTemplates
Type:
type injectSpritesInTemplates = boolean;Default: false
Enables the plugin to inject sprites into HTML files generated by html-webpack-plugin. The sprites will be injected at the beginning of the content <body>.
[!NOTE] The sprites associated with the entrypoints listed in the
chunksparameter of HtmlWebpackPlugin will be automatically included in the corresponding HTML files.
webpack.config.js
export default {
plugins: [
new SvgChunkWebpackPlugin({
injectSpritesInTemplates: true
})
]
};Caching
With webpack caching, several placeholders are available depending on your needs. With SVG inlined in the page, this option is not useful.
[!NOTE] The
[contenthash]placeholder is the best option because it depends on the sprite content. Cache placeholders are expensive in build performance, use it only in production mode.
[contenthash]
The [contenthash] placeholder will add a unique hash based on the content of the sprite. When the sprite's content changes, the hash will change as well.
webpack.config.js
export default {
plugins: [
new SvgChunkWebpackPlugin({
filename: '[name].[contenthash].svg'
})
]
};[fullhash]
The [fullhash] placeholder will add a unique hash generated for every build. When the compilation build is updated, the hash will change as well.
webpack.config.js
export default {
plugins: [
new SvgChunkWebpackPlugin({
filename: '[name].[fullhash].svg'
})
]
};License
svg-chunk-webpack-plugin is licensed under the MIT License.
Created with ♥ by @yoriiis.
VirtualUrlPlugin
다양한 유형의 가상 모듈을 생성할 수 있습니다. 예를 들어 .ts, .json, .css 등이 있으며, 기본값은 .js입니다.
import webpack from "webpack";
new webpack.experiments.schemes.VirtualUrlPlugin({
myModule: 'export const msg = "from virtual module"',
});src/app.js
import { msg } from "virtual:myModule";
console.log(msg);Basic Example
빌드 정보를 생성하는 가상 모듈을 만듭니다.
import webpack from "webpack";
new webpack.experiments.schemes.VirtualUrlPlugin({
buildInfo: {
source() {
return `export const buildTime = ${Date.now()}`;
},
version: true,
},
});src/app.js
import { buildTime } from "virtual:buildInfo";
console.log(`App version: ${buildTime}`);사용자 정의 스키마 사용
import webpack from "webpack";
new webpack.experiments.schemes.VirtualUrlPlugin(
{
myModule: 'export const msg = "from virtual module"',
},
"v",
);src/app.js
import { msg } from "v:myModule";
console.log(msg);Advanced Example
다양한 유형의 가상 모듈을 여러 개 생성합니다.
import webpack from "webpack";
new webpack.experiments.schemes.VirtualUrlPlugin({
myCssModule: {
type: ".css",
source: "body{background-color: powderblue;}",
},
myJsonModule: {
type: ".json",
source: '{"name": "virtual-url-plugin"}',
},
});src/app.js
import json from "virtual:myJsonModule";
import "virtual:myCssModule";라우팅 파일을 가상화합니다.
import path from "node:path";
import { fileURLToPath } from "node:url";
import webpack from "webpack";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const watchDir = path.join(__dirname, "./src/routes");
new webpack.experiments.schemes.VirtualUrlPlugin({
routes: {
source(loaderContext) {
// Use addContextDependency to monitor the addition or removal of subdirectories in watchDir to trigger the rebuilding of virtual modules.
loaderContext.addContextDependency(watchDir);
const files = fs.readdirSync(watchDir);
return `
export const routes = {
${files.map((key) => `${key.split(".")[0]}: () => import('./src/routes/${key}')`).join(",\n")}
}
`;
},
},
});src/app.js
import { routes } from "virtual:routes";Options
module.type(string): 가상 모듈의 콘텐츠 유형.
-
module.source(string | ((loaderContext: import('webpack').LoaderContext<T>) => Promise<string> | string)): 가상 모듈의 콘텐츠를 생성하는 팩토리 함수. -
module.version(boolean | string | () => string): 무효화가 발생하면 버전의 값이 이전과 다를 경우 소스 함수가 다시 호출됩니다. true로 설정하면 항상 트리거됩니다. -
scheme(string): 가상 모듈의 URL 스키마, 기본값은'virtual'입니다. 두 번째 생성자 인자는 스키마 문자열이거나 객체{ scheme?, context? }입니다.
WatchIgnorePlugin
watch 모드인 경우, 제공된 경로나 정규 표현식에 해당되는 특정 파일을 무시합니다.
new webpack.WatchIgnorePlugin({ paths });Options
paths(Array<string | RegExp>): 무시해야 하는 디렉터리나 파일에 대한 정규 표현식 또는 절대 경로의 목록입니다.