Skip to content

Commit 09b1dc9

Browse files
feat(webpack@5): improve stats for copied assets
1 parent d8b4a72 commit 09b1dc9

File tree

8 files changed

+1067
-1697
lines changed

8 files changed

+1067
-1697
lines changed

.github/workflows/nodejs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ jobs:
4343
- name: Lint
4444
run: npm run lint
4545

46-
# - name: Security audit
47-
# run: npm run security
46+
- name: Security audit
47+
run: npm run security
4848

4949
- name: Check commit message
5050
uses: wagoid/commitlint-github-action@v1

package-lock.json

Lines changed: 906 additions & 1643 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,35 +49,35 @@
4949
"loader-utils": "^2.0.0",
5050
"normalize-path": "^3.0.0",
5151
"p-limit": "^3.0.2",
52-
"schema-utils": "^2.7.0",
52+
"schema-utils": "^2.7.1",
5353
"serialize-javascript": "^4.0.0",
5454
"webpack-sources": "^1.4.3"
5555
},
5656
"devDependencies": {
5757
"@babel/cli": "^7.10.5",
58-
"@babel/core": "^7.11.1",
58+
"@babel/core": "^7.11.4",
5959
"@babel/preset-env": "^7.11.0",
60-
"@commitlint/cli": "^9.1.1",
61-
"@commitlint/config-conventional": "^9.1.1",
60+
"@commitlint/cli": "^10.0.0",
61+
"@commitlint/config-conventional": "^10.0.0",
6262
"@webpack-contrib/defaults": "^6.3.0",
6363
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
6464
"babel-jest": "^26.3.0",
6565
"chokidar": "^3.4.2",
6666
"cross-env": "^7.0.2",
6767
"del": "^5.1.0",
6868
"del-cli": "^3.0.1",
69-
"eslint": "^7.6.0",
69+
"eslint": "^7.7.0",
7070
"eslint-config-prettier": "^6.11.0",
7171
"eslint-plugin-import": "^2.22.0",
7272
"husky": "^4.2.5",
7373
"is-gzip": "^2.0.0",
74-
"jest": "^26.3.0",
75-
"lint-staged": "^10.2.11",
74+
"jest": "^26.4.2",
75+
"lint-staged": "^10.2.13",
7676
"memfs": "^3.2.0",
7777
"mkdirp": "^1.0.4",
7878
"npm-run-all": "^4.1.5",
79-
"prettier": "^2.0.5",
80-
"standard-version": "^8.0.2",
79+
"prettier": "^2.1.1",
80+
"standard-version": "^9.0.0",
8181
"webpack": "^4.44.1"
8282
},
8383
"keywords": [

src/index.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ class CopyPlugin {
2424
}
2525

2626
apply(compiler) {
27-
const plugin = { name: 'CopyPlugin' };
27+
const pluginName = this.constructor.name;
2828
const limit = pLimit(this.options.concurrency || 100);
2929

30-
compiler.hooks.thisCompilation.tap(plugin, (compilation) => {
30+
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
3131
const logger = compilation.getLogger('copy-webpack-plugin');
3232

3333
compilation.hooks.additionalAssets.tapAsync(
@@ -110,13 +110,15 @@ class CopyPlugin {
110110
return;
111111
}
112112

113-
if (compilation.getAsset(targetPath)) {
113+
const info = compilation.getAsset(targetPath);
114+
115+
if (info) {
114116
if (force) {
115117
logger.log(
116118
`force updating '${webpackTo}' to compilation assets from '${absoluteFrom}'`
117119
);
118120

119-
compilation.updateAsset(targetPath, source);
121+
compilation.updateAsset(targetPath, source, { copied: true });
120122

121123
return;
122124
}
@@ -132,14 +134,25 @@ class CopyPlugin {
132134
`writing '${webpackTo}' to compilation assets from '${absoluteFrom}'`
133135
);
134136

135-
compilation.emitAsset(targetPath, source);
137+
compilation.emitAsset(targetPath, source, { copied: true });
136138
});
137139

138140
logger.debug('end to adding additional assets');
139141

140142
callback();
141143
}
142144
);
145+
146+
if (compilation.hooks.statsPrinter) {
147+
compilation.hooks.statsPrinter.tap(pluginName, (stats) => {
148+
stats.hooks.print
149+
.for('asset.info.copied')
150+
.tap('copy-webpack-plugin', (copied, { green, formatFlag }) =>
151+
// eslint-disable-next-line no-undefined
152+
copied ? green(formatFlag('copied')) : undefined
153+
);
154+
});
155+
}
143156
});
144157
}
145158
}

test/CopyPlugin.test.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import path from 'path';
22

3+
import webpack from 'webpack';
4+
35
import CopyPlugin from '../src';
46

57
import { run, runEmit, runChange } from './helpers/run';
@@ -257,6 +259,82 @@ describe('CopyPlugin', () => {
257259
.then(done)
258260
.catch(done);
259261
});
262+
263+
it('should copy files with "copied" flags', (done) => {
264+
expect.assertions(5);
265+
266+
const expectedAssetKeys = [
267+
'.dottedfile',
268+
'directoryfile.txt',
269+
'nested/deep-nested/deepnested.txt',
270+
'nested/nestedfile.txt',
271+
];
272+
273+
run({
274+
preCopy: {
275+
additionalAssets: [
276+
{ name: 'foo-bar.txt', data: 'Content', info: { custom: true } },
277+
{
278+
name: 'nested/nestedfile.txt',
279+
data: 'Content',
280+
info: { custom: true },
281+
},
282+
],
283+
},
284+
expectedAssetKeys,
285+
patterns: [
286+
{
287+
from: 'directory',
288+
force: true,
289+
},
290+
],
291+
})
292+
.then(({ stats }) => {
293+
for (const name of expectedAssetKeys) {
294+
const info = stats.compilation.assetsInfo.get(name);
295+
296+
expect(info.copied).toBe(true);
297+
298+
if (name === 'nested/nestedfile.txt') {
299+
expect(info.custom).toBe(true);
300+
}
301+
}
302+
})
303+
.then(done)
304+
.catch(done);
305+
});
306+
307+
it('should copy files and print "copied" in the string representation ', (done) => {
308+
const isWebpack4 = webpack.version[0] === '4';
309+
310+
expect.assertions(isWebpack4 ? 0 : 1);
311+
312+
const expectedAssetKeys = [
313+
'.dottedfile',
314+
'directoryfile.txt',
315+
'nested/deep-nested/deepnested.txt',
316+
'nested/nestedfile.txt',
317+
];
318+
319+
run({
320+
withExistingAsset: true,
321+
expectedAssetKeys,
322+
patterns: [
323+
{
324+
from: 'directory',
325+
},
326+
],
327+
})
328+
.then(({ stats }) => {
329+
const stringStats = stats.toString();
330+
331+
if (!isWebpack4) {
332+
expect(stringStats.match(/\[copied]/g).length).toBe(4);
333+
}
334+
})
335+
.then(done)
336+
.catch(done);
337+
});
260338
});
261339

262340
describe('watch mode', () => {

test/force-option.test.js

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ describe('force option', () => {
44
describe('is not specified', () => {
55
it('should not overwrite a file already in the compilation by default when "from" is a file', (done) => {
66
runForce({
7-
existingAssets: ['file.txt'],
7+
additionalAssets: [{ name: 'file.txt', data: 'existing' }],
88
expectedAssetKeys: ['file.txt'],
99
expectedAssetContent: {
1010
'file.txt': 'existing',
@@ -21,11 +21,11 @@ describe('force option', () => {
2121

2222
it('should not overwrite files already in the compilation when "from" is a directory', (done) => {
2323
runForce({
24-
existingAssets: [
25-
'.dottedfile',
26-
'directoryfile.txt',
27-
'nested/deep-nested/deepnested.txt',
28-
'nested/nestedfile.txt',
24+
additionalAssets: [
25+
{ name: '.dottedfile', data: 'existing' },
26+
{ name: 'directoryfile.txt', data: 'existing' },
27+
{ name: 'nested/deep-nested/deepnested.txt', data: 'existing' },
28+
{ name: 'nested/nestedfile.txt', data: 'existing' },
2929
],
3030
expectedAssetKeys: [
3131
'.dottedfile',
@@ -51,10 +51,13 @@ describe('force option', () => {
5151

5252
it('should not overwrite files already in the compilation when "from" is a glob', (done) => {
5353
runForce({
54-
existingAssets: [
55-
'directory/directoryfile.txt',
56-
'directory/nested/deep-nested/deepnested.txt',
57-
'directory/nested/nestedfile.txt',
54+
additionalAssets: [
55+
{ name: 'directory/directoryfile.txt', data: 'existing' },
56+
{
57+
name: 'directory/nested/deep-nested/deepnested.txt',
58+
data: 'existing',
59+
},
60+
{ name: 'directory/nested/nestedfile.txt', data: 'existing' },
5861
],
5962
expectedAssetKeys: [
6063
'directory/directoryfile.txt',
@@ -80,7 +83,7 @@ describe('force option', () => {
8083
describe('is "false" (Boolean)', () => {
8184
it('should not overwrite a file already in the compilation by default when "from" is a file', (done) => {
8285
runForce({
83-
existingAssets: ['file.txt'],
86+
additionalAssets: [{ name: 'file.txt', data: 'existing' }],
8487
expectedAssetKeys: ['file.txt'],
8588
expectedAssetContent: {
8689
'file.txt': 'existing',
@@ -98,11 +101,11 @@ describe('force option', () => {
98101

99102
it('should not overwrite files already in the compilation when "from" is a directory', (done) => {
100103
runForce({
101-
existingAssets: [
102-
'.dottedfile',
103-
'directoryfile.txt',
104-
'nested/deep-nested/deepnested.txt',
105-
'nested/nestedfile.txt',
104+
additionalAssets: [
105+
{ name: '.dottedfile', data: 'existing' },
106+
{ name: 'directoryfile.txt', data: 'existing' },
107+
{ name: 'nested/deep-nested/deepnested.txt', data: 'existing' },
108+
{ name: 'nested/nestedfile.txt', data: 'existing' },
106109
],
107110
expectedAssetKeys: [
108111
'.dottedfile',
@@ -129,10 +132,13 @@ describe('force option', () => {
129132

130133
it('should not overwrite files already in the compilation when "from" is a glob', (done) => {
131134
runForce({
132-
existingAssets: [
133-
'directory/directoryfile.txt',
134-
'directory/nested/deep-nested/deepnested.txt',
135-
'directory/nested/nestedfile.txt',
135+
additionalAssets: [
136+
{ name: 'directory/directoryfile.txt', data: 'existing' },
137+
{
138+
name: 'directory/nested/deep-nested/deepnested.txt',
139+
data: 'existing',
140+
},
141+
{ name: 'directory/nested/nestedfile.txt', data: 'existing' },
136142
],
137143
expectedAssetKeys: [
138144
'directory/directoryfile.txt',
@@ -159,7 +165,7 @@ describe('force option', () => {
159165
describe('is "true" (Boolean)', () => {
160166
it('should force overwrite a file already in the compilation when "from" is a file', (done) => {
161167
runForce({
162-
existingAssets: ['file.txt'],
168+
additionalAssets: [{ name: 'file.txt', data: 'existing' }],
163169
expectedAssetKeys: ['file.txt'],
164170
expectedAssetContent: {
165171
'file.txt': 'new',
@@ -177,11 +183,11 @@ describe('force option', () => {
177183

178184
it('should force overwrite files already in the compilation when "from" is a directory', (done) => {
179185
runForce({
180-
existingAssets: [
181-
'.dottedfile',
182-
'directoryfile.txt',
183-
'nested/deep-nested/deepnested.txt',
184-
'nested/nestedfile.txt',
186+
additionalAssets: [
187+
{ name: '.dottedfile', data: 'existing' },
188+
{ name: 'directoryfile.txt', data: 'existing' },
189+
{ name: 'nested/deep-nested/deepnested.txt', data: 'existing' },
190+
{ name: 'nested/nestedfile.txt', data: 'existing' },
185191
],
186192
expectedAssetKeys: [
187193
'.dottedfile',
@@ -208,10 +214,13 @@ describe('force option', () => {
208214

209215
it('should force overwrite files already in the compilation when "from" is a glob', (done) => {
210216
runForce({
211-
existingAssets: [
212-
'directory/directoryfile.txt',
213-
'directory/nested/deep-nested/deepnested.txt',
214-
'directory/nested/nestedfile.txt',
217+
additionalAssets: [
218+
{ name: 'directory/directoryfile.txt', data: 'existing' },
219+
{
220+
name: 'directory/nested/deep-nested/deepnested.txt',
221+
data: 'existing',
222+
},
223+
{ name: 'directory/nested/nestedfile.txt', data: 'existing' },
215224
],
216225
expectedAssetKeys: [
217226
'directory/directoryfile.txt',

test/helpers/PreCopyPlugin.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import webpack from 'webpack';
2+
3+
const { RawSource } =
4+
// eslint-disable-next-line global-require
5+
webpack.sources || require('webpack-sources');
6+
17
class PreCopyPlugin {
28
constructor(options = {}) {
39
this.options = options.options || {};
@@ -8,15 +14,12 @@ class PreCopyPlugin {
814

915
compiler.hooks.thisCompilation.tap(plugin, (compilation) => {
1016
compilation.hooks.additionalAssets.tapAsync(
11-
'copy-webpack-plugin',
17+
'pre-copy-webpack-plugin',
1218
(callback) => {
13-
this.options.existingAssets.forEach((assetName) => {
14-
// eslint-disable-next-line no-param-reassign
15-
compilation.assets[assetName] = {
16-
source() {
17-
return 'existing';
18-
},
19-
};
19+
this.options.additionalAssets.forEach(({ name, data, info }) => {
20+
const source = new RawSource(data);
21+
22+
compilation.emitAsset(name, source, info);
2023
});
2124

2225
callback();

test/helpers/run.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ function run(opts) {
4545

4646
const compiler = opts.compiler || getCompiler();
4747

48+
if (opts.preCopy) {
49+
new PreCopyPlugin({ options: opts.preCopy }).apply(compiler);
50+
}
51+
4852
new CopyPlugin({ patterns: opts.patterns, options: opts.options }).apply(
4953
compiler
5054
);

0 commit comments

Comments
 (0)