Skip to content

Commit bdb1292

Browse files
authored
perf(v2): fork clean-webpack-plugin to reduce memory (#1839)
* chore: fork clean-webpack-plugin * deps
1 parent 7f3146a commit bdb1292

File tree

5 files changed

+336
-10
lines changed

5 files changed

+336
-10
lines changed

CHANGELOG-2.x.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Docusaurus 2 Changelog
22

3+
## 2.0.0-alpha.28
4+
- Further reduce memory usage to avoid heap memory allocation failure.
5+
36
## 2.0.0-alpha.27
47

58
- Add `@theme/Tabs` which can be used to implement multi-language code tabs.

packages/docusaurus/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@
4343
"chalk": "^2.4.2",
4444
"chokidar": "^3.0.2",
4545
"classnames": "^2.2.6",
46-
"clean-webpack-plugin": "^2.0.1",
4746
"commander": "^2.20.0",
4847
"copy-webpack-plugin": "^5.0.4",
4948
"css-loader": "^3.1.0",
49+
"del": "^4.1.1",
5050
"ejs": "^2.6.2",
5151
"express": "^4.17.1",
5252
"fs-extra": "^8.1.0",

packages/docusaurus/src/commands/build.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
import chalk from 'chalk';
9-
import CleanWebpackPlugin from 'clean-webpack-plugin';
109
import CopyWebpackPlugin from 'copy-webpack-plugin';
1110
import fs from 'fs-extra';
1211
import path from 'path';
@@ -20,6 +19,7 @@ import {CLIOptions, Props} from '@docusaurus/types';
2019
import {createClientConfig} from '../webpack/client';
2120
import {createServerConfig} from '../webpack/server';
2221
import {applyConfigureWebpack} from '../webpack/utils';
22+
import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin';
2323

2424
function compile(config: Configuration[]): Promise<any> {
2525
return new Promise((resolve, reject) => {
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
/**
2+
* Copyright (c) 2017-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {Compiler, Stats} from 'webpack';
9+
import path from 'path';
10+
import {sync as delSync} from 'del';
11+
12+
export interface Options {
13+
/**
14+
* Simulate the removal of files
15+
*
16+
* default: false
17+
*/
18+
dry?: boolean;
19+
20+
/**
21+
* Write Logs to Console
22+
* (Always enabled when dry is true)
23+
*
24+
* default: false
25+
*/
26+
verbose?: boolean;
27+
28+
/**
29+
* Automatically remove all unused webpack assets on rebuild
30+
*
31+
* default: true
32+
*/
33+
cleanStaleWebpackAssets?: boolean;
34+
35+
/**
36+
* Do not allow removal of current webpack assets
37+
*
38+
* default: true
39+
*/
40+
protectWebpackAssets?: boolean;
41+
42+
/**
43+
* Removes files once prior to Webpack compilation
44+
* Not included in rebuilds (watch mode)
45+
*
46+
* Use !negative patterns to exclude files
47+
*
48+
* default: ['**\/*']
49+
*/
50+
cleanOnceBeforeBuildPatterns?: string[];
51+
52+
/**
53+
* Removes files after every build (including watch mode) that match this pattern.
54+
* Used for files that are not created directly by Webpack.
55+
*
56+
* Use !negative patterns to exclude files
57+
*
58+
* default: disabled
59+
*/
60+
cleanAfterEveryBuildPatterns?: string[];
61+
62+
/**
63+
* Allow clean patterns outside of process.cwd()
64+
*
65+
* requires dry option to be explicitly set
66+
*
67+
* default: false
68+
*/
69+
dangerouslyAllowCleanPatternsOutsideProject?: boolean;
70+
}
71+
72+
class CleanWebpackPlugin {
73+
private readonly dry: boolean;
74+
private readonly verbose: boolean;
75+
private readonly cleanStaleWebpackAssets: boolean;
76+
private readonly protectWebpackAssets: boolean;
77+
private readonly cleanAfterEveryBuildPatterns: string[];
78+
private readonly cleanOnceBeforeBuildPatterns: string[];
79+
private readonly dangerouslyAllowCleanPatternsOutsideProject: boolean;
80+
private currentAssets: string[];
81+
private initialClean: boolean;
82+
private outputPath: string;
83+
84+
constructor(options: Options = {}) {
85+
if (typeof options !== 'object' || Array.isArray(options) === true) {
86+
throw new Error(`clean-webpack-plugin only accepts an options object. See:
87+
https://github.com/johnagan/clean-webpack-plugin#options-and-defaults-optional`);
88+
}
89+
90+
// @ts-ignore
91+
if (options.allowExternal) {
92+
throw new Error(
93+
'clean-webpack-plugin: `allowExternal` option no longer supported. Use `dangerouslyAllowCleanPatternsOutsideProject`',
94+
);
95+
}
96+
97+
if (
98+
options.dangerouslyAllowCleanPatternsOutsideProject === true &&
99+
options.dry !== true &&
100+
options.dry !== false
101+
) {
102+
// eslint-disable-next-line no-console
103+
console.warn(
104+
'clean-webpack-plugin: dangerouslyAllowCleanPatternsOutsideProject requires dry: false to be explicitly set. Enabling dry mode',
105+
);
106+
}
107+
108+
this.dangerouslyAllowCleanPatternsOutsideProject =
109+
options.dangerouslyAllowCleanPatternsOutsideProject === true || false;
110+
111+
this.dry =
112+
options.dry === true || options.dry === false
113+
? options.dry
114+
: this.dangerouslyAllowCleanPatternsOutsideProject === true || false;
115+
116+
this.verbose = this.dry === true || options.verbose === true || false;
117+
118+
this.cleanStaleWebpackAssets =
119+
options.cleanStaleWebpackAssets === true ||
120+
options.cleanStaleWebpackAssets === false
121+
? options.cleanStaleWebpackAssets
122+
: true;
123+
124+
this.protectWebpackAssets =
125+
options.protectWebpackAssets === true ||
126+
options.protectWebpackAssets === false
127+
? options.protectWebpackAssets
128+
: true;
129+
130+
this.cleanAfterEveryBuildPatterns = Array.isArray(
131+
options.cleanAfterEveryBuildPatterns,
132+
)
133+
? options.cleanAfterEveryBuildPatterns
134+
: [];
135+
136+
this.cleanOnceBeforeBuildPatterns = Array.isArray(
137+
options.cleanOnceBeforeBuildPatterns,
138+
)
139+
? options.cleanOnceBeforeBuildPatterns
140+
: ['**/*'];
141+
142+
/**
143+
* Store webpack build assets
144+
*/
145+
this.currentAssets = [];
146+
147+
/**
148+
* Only used with cleanOnceBeforeBuildPatterns
149+
*/
150+
this.initialClean = false;
151+
152+
this.outputPath = '';
153+
154+
this.apply = this.apply.bind(this);
155+
this.handleInitial = this.handleInitial.bind(this);
156+
this.handleDone = this.handleDone.bind(this);
157+
this.removeFiles = this.removeFiles.bind(this);
158+
}
159+
160+
apply(compiler: Compiler) {
161+
if (!compiler.options.output || !compiler.options.output.path) {
162+
// eslint-disable-next-line no-console
163+
console.warn(
164+
'clean-webpack-plugin: options.output.path not defined. Plugin disabled...',
165+
);
166+
167+
return;
168+
}
169+
170+
this.outputPath = compiler.options.output.path;
171+
172+
/**
173+
* webpack 4+ comes with a new plugin system.
174+
*
175+
* Check for hooks in-order to support old plugin system
176+
*/
177+
const hooks = compiler.hooks;
178+
179+
if (this.cleanOnceBeforeBuildPatterns.length !== 0) {
180+
if (hooks) {
181+
hooks.compile.tap('clean-webpack-plugin', () => {
182+
this.handleInitial();
183+
});
184+
} else {
185+
compiler.plugin('compile', () => {
186+
this.handleInitial();
187+
});
188+
}
189+
}
190+
191+
if (hooks) {
192+
hooks.done.tap('clean-webpack-plugin', stats => {
193+
this.handleDone(stats);
194+
});
195+
} else {
196+
compiler.plugin('done', stats => {
197+
this.handleDone(stats);
198+
});
199+
}
200+
}
201+
202+
/**
203+
* Initially remove files from output directory prior to build.
204+
*
205+
* Only happens once.
206+
*
207+
* Warning: It is recommended to initially clean your build directory outside of webpack to minimize unexpected behavior.
208+
*/
209+
handleInitial() {
210+
if (this.initialClean) {
211+
return;
212+
}
213+
214+
this.initialClean = true;
215+
216+
this.removeFiles(this.cleanOnceBeforeBuildPatterns);
217+
}
218+
219+
handleDone(stats: Stats) {
220+
/**
221+
* Do nothing if there is a webpack error
222+
*/
223+
if (stats.hasErrors()) {
224+
if (this.verbose) {
225+
// eslint-disable-next-line no-console
226+
console.warn('clean-webpack-plugin: pausing due to webpack errors');
227+
}
228+
229+
return;
230+
}
231+
232+
/**
233+
* Fetch Webpack's output asset files
234+
*/
235+
const statsAssets =
236+
stats.toJson(
237+
{
238+
all: false,
239+
assets: true,
240+
},
241+
true,
242+
).assets || [];
243+
const assets = statsAssets.map((asset: {name: string}) => {
244+
return asset.name;
245+
});
246+
247+
/**
248+
* Get all files that were in the previous build but not the current
249+
*
250+
* (relies on del's cwd: outputPath option)
251+
*/
252+
const staleFiles = this.currentAssets.filter(previousAsset => {
253+
const assetCurrent = assets.includes(previousAsset) === false;
254+
255+
return assetCurrent;
256+
});
257+
258+
/**
259+
* Save assets for next compilation
260+
*/
261+
this.currentAssets = assets.sort();
262+
263+
const removePatterns: string[] = [];
264+
265+
/**
266+
* Remove unused webpack assets
267+
*/
268+
if (this.cleanStaleWebpackAssets === true && staleFiles.length !== 0) {
269+
removePatterns.push(...staleFiles);
270+
}
271+
272+
/**
273+
* Remove cleanAfterEveryBuildPatterns
274+
*/
275+
if (this.cleanAfterEveryBuildPatterns.length !== 0) {
276+
removePatterns.push(...this.cleanAfterEveryBuildPatterns);
277+
}
278+
279+
if (removePatterns.length !== 0) {
280+
this.removeFiles(removePatterns);
281+
}
282+
}
283+
284+
removeFiles(patterns: string[]) {
285+
try {
286+
const deleted = delSync(patterns, {
287+
force: this.dangerouslyAllowCleanPatternsOutsideProject,
288+
// Change context to build directory
289+
cwd: this.outputPath,
290+
dryRun: this.dry,
291+
dot: true,
292+
ignore: this.protectWebpackAssets ? this.currentAssets : [],
293+
});
294+
295+
/**
296+
* Log if verbose is enabled
297+
*/
298+
if (this.verbose) {
299+
deleted.forEach(file => {
300+
const filename = path.relative(process.cwd(), file);
301+
302+
const message = this.dry ? 'dry' : 'removed';
303+
304+
/**
305+
* Use console.warn over .log
306+
* https://github.com/webpack/webpack/issues/1904
307+
* https://github.com/johnagan/clean-webpack-plugin/issues/11
308+
*/
309+
// eslint-disable-next-line no-console
310+
console.warn(`clean-webpack-plugin: ${message} ${filename}`);
311+
});
312+
}
313+
} catch (error) {
314+
const needsForce = /Cannot delete files\/folders outside the current working directory\./.test(
315+
error.message,
316+
);
317+
318+
if (needsForce) {
319+
const message =
320+
'clean-webpack-plugin: Cannot delete files/folders outside the current working directory. Can be overridden with the `dangerouslyAllowCleanPatternsOutsideProject` option.';
321+
322+
throw new Error(message);
323+
}
324+
325+
throw error;
326+
}
327+
}
328+
}
329+
330+
export default CleanWebpackPlugin;

yarn.lock

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4486,13 +4486,6 @@ clean-stack@^2.0.0:
44864486
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
44874487
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
44884488

4489-
clean-webpack-plugin@^2.0.1:
4490-
version "2.0.2"
4491-
resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-2.0.2.tgz#805a19ff20d46a06125298a25eb31142ecad2166"
4492-
integrity sha512-pi1111o4OBd9qvacbgs+NRqClfVPKVIc66B4d8kx6Ho/L+i9entQ/NpK600CsTYTPu3kWvKwwyKarsYMvC2xeA==
4493-
dependencies:
4494-
del "^4.0.0"
4495-
44964489
cli-cursor@^2.0.0, cli-cursor@^2.1.0:
44974490
version "2.1.0"
44984491
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
@@ -5543,7 +5536,7 @@ define-property@^2.0.2:
55435536
is-descriptor "^1.0.2"
55445537
isobject "^3.0.1"
55455538

5546-
del@^4.0.0, del@^4.1.1:
5539+
del@^4.1.1:
55475540
version "4.1.1"
55485541
resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4"
55495542
integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==

0 commit comments

Comments
 (0)