Skip to content

Commit dde71f0

Browse files
authoredMar 22, 2021
feat: added the transformAll option (#596)
1 parent 4ca7f80 commit dde71f0

7 files changed

+453
-35
lines changed
 

‎README.md

+40
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ module.exports = {
8787
| [`force`](#force) | `{Boolean}` | `false` | Overwrites files already in `compilation.assets` (usually added by other plugins/loaders). |
8888
| [`priority`](#priority) | `{Number}` | `0` | Allows you to specify the copy priority. |
8989
| [`transform`](#transform) | `{Object}` | `undefined` | Allows to modify the file contents. Enable `transform` caching. You can use `{ transform: {cache: { key: 'my-cache-key' }} }` to invalidate the cache. |
90+
| [`transformAll`](#transformAll) | `{Function}` | `undefined` | Allows you to modify the contents of multiple files and save the result to one file. |
9091
| [`noErrorOnMissing`](#noerroronmissing) | `{Boolean}` | `false` | Doesn't generate an error on missing file(s). |
9192
| [`info`](#info) | `{Object\|Function}` | `undefined` | Allows to add assets info. |
9293

@@ -730,6 +731,45 @@ module.exports = {
730731
};
731732
```
732733

734+
#### `transformAll`
735+
736+
Type: `Function`
737+
Default: `undefined`
738+
739+
Allows you to modify the contents of multiple files and save the result to one file.
740+
741+
> ℹ️ The `to` option must be specified and point to a file. It is allowed to use only `[contenthash]` and `[fullhash]` template strings.
742+
743+
**webpack.config.js**
744+
745+
```js
746+
module.exports = {
747+
plugins: [
748+
new CopyPlugin({
749+
patterns: [
750+
{
751+
from: "src/**/*.txt",
752+
to: "dest/file.txt",
753+
// The `assets` argument is an assets array for the pattern.from ("src/**/*.txt")
754+
transformAll(assets) {
755+
const result = assets.reduce((accumulator, asset) => {
756+
// The asset content can be obtained from `asset.source` using `source` method.
Has conversations. Original line has conversations.
757+
// The asset content is a [`Buffer`](https://nodejs.org/api/buffer.html) object, it could be converted to a `String` to be processed using `content.toString()`
758+
const content = asset.data;
759+
760+
accumulator = `${accumulator}${content}\n`;
761+
return accumulator;
762+
}, "");
763+
764+
return result;
765+
},
766+
},
767+
],
768+
}),
769+
],
770+
};
771+
```
772+
733773
### `noErrorOnMissing`
734774

735775
Type: `Boolean`

‎src/index.js

+130-18
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,27 @@ class CopyPlugin {
6969
});
7070
}
7171

72+
static getContentHash(compiler, compilation, source) {
73+
const { outputOptions } = compilation;
74+
const {
75+
hashDigest,
76+
hashDigestLength,
77+
hashFunction,
78+
hashSalt,
79+
} = outputOptions;
80+
const hash = compiler.webpack.util.createHash(hashFunction);
81+
82+
if (hashSalt) {
83+
hash.update(hashSalt);
84+
}
85+
86+
hash.update(source);
87+
88+
const fullContentHash = hash.digest(hashDigest);
89+
90+
return fullContentHash.slice(0, hashDigestLength);
91+
}
92+
7293
static async runPattern(
7394
compiler,
7495
compilation,
@@ -456,7 +477,7 @@ class CopyPlugin {
456477
if (transform.transformer) {
457478
logger.log(`transforming content for '${absoluteFilename}'...`);
458479

459-
const buffer = result.source.source();
480+
const buffer = result.source.buffer();
460481

461482
if (transform.cache) {
462483
const defaultCacheKeys = {
@@ -526,23 +547,11 @@ class CopyPlugin {
526547
`interpolating template '${filename}' for '${sourceFilename}'...`
527548
);
528549

529-
const { outputOptions } = compilation;
530-
const {
531-
hashDigest,
532-
hashDigestLength,
533-
hashFunction,
534-
hashSalt,
535-
} = outputOptions;
536-
const hash = compiler.webpack.util.createHash(hashFunction);
537-
538-
if (hashSalt) {
539-
hash.update(hashSalt);
540-
}
541-
542-
hash.update(result.source.source());
543-
544-
const fullContentHash = hash.digest(hashDigest);
545-
const contentHash = fullContentHash.slice(0, hashDigestLength);
550+
const contentHash = CopyPlugin.getContentHash(
551+
compiler,
552+
compilation,
553+
result.source.buffer()
554+
);
546555
const ext = path.extname(result.sourceFilename);
547556
const base = path.basename(result.sourceFilename);
548557
const name = base.slice(0, base.length - ext.length);
@@ -634,6 +643,109 @@ class CopyPlugin {
634643
}
635644

636645
if (assets && assets.length > 0) {
646+
if (item.transformAll) {
647+
if (typeof item.to === "undefined") {
648+
compilation.errors.push(
649+
new Error(
650+
`Invalid "pattern.to" for the "pattern.from": "${item.from}" and "pattern.transformAll" function. The "to" option must be specified.`
651+
)
652+
);
653+
654+
return;
655+
}
656+
657+
assets.sort((a, b) =>
658+
a.absoluteFilename > b.absoluteFilename
659+
? 1
660+
: a.absoluteFilename < b.absoluteFilename
661+
? -1
662+
: 0
663+
);
664+
665+
const mergedEtag =
666+
assets.length === 1
667+
? cache.getLazyHashedEtag(assets[0].source.buffer())
668+
: assets.reduce((accumulator, asset, i) => {
669+
// eslint-disable-next-line no-param-reassign
670+
accumulator = cache.mergeEtags(
671+
i === 1
672+
? cache.getLazyHashedEtag(
673+
accumulator.source.buffer()
674+
)
675+
: accumulator,
676+
cache.getLazyHashedEtag(asset.source.buffer())
677+
);
678+
679+
return accumulator;
680+
});
681+
682+
const cacheKeys = `transformAll|${serialize({
683+
version,
684+
from: item.from,
685+
to: item.to,
686+
transformAll: item.transformAll,
687+
})}`;
688+
const eTag = cache.getLazyHashedEtag(mergedEtag);
689+
const cacheItem = cache.getItemCache(cacheKeys, eTag);
690+
let transformedAsset = await cacheItem.getPromise();
691+
692+
if (!transformedAsset) {
693+
transformedAsset = { filename: item.to };
694+
695+
try {
696+
transformedAsset.data = await item.transformAll(
697+
assets.map((asset) => {
698+
return {
699+
data: asset.source.buffer(),
700+
sourceFilename: asset.sourceFilename,
701+
absoluteFilename: asset.absoluteFilename,
702+
};
703+
})
704+
);
705+
} catch (error) {
706+
compilation.errors.push(error);
707+
708+
return;
709+
}
710+
711+
if (template.test(item.to)) {
712+
const contentHash = CopyPlugin.getContentHash(
713+
compiler,
714+
compilation,
715+
transformedAsset.data
716+
);
717+
718+
const {
719+
path: interpolatedFilename,
720+
info: assetInfo,
721+
} = compilation.getPathWithInfo(
722+
normalizePath(item.to),
723+
{
724+
contentHash,
725+
chunk: {
726+
hash: contentHash,
727+
contentHash,
728+
},
729+
}
730+
);
731+
732+
transformedAsset.filename = interpolatedFilename;
733+
transformedAsset.info = assetInfo;
734+
}
735+
736+
const { RawSource } = compiler.webpack.sources;
737+
738+
transformedAsset.source = new RawSource(
739+
transformedAsset.data
740+
);
741+
transformedAsset.force = item.force;
742+
743+
await cacheItem.storePromise(transformedAsset);
744+
}
745+
746+
assets = [transformedAsset];
747+
}
748+
637749
const priority = item.priority || 0;
638750

639751
if (!assetMap.has(priority)) {

‎src/options.json

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
"filter": {
2828
"instanceof": "Function"
2929
},
30+
"transformAll": {
31+
"instanceof": "Function"
32+
},
3033
"toType": {
3134
"enum": ["dir", "file", "template"]
3235
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`cache should work with the "memory" cache: assets 1`] = `
4+
Object {
5+
"file.txt": "new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
6+
}
7+
`;
8+
9+
exports[`cache should work with the "memory" cache: assets 2`] = `
10+
Object {
11+
"file.txt": "new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
12+
}
13+
`;
14+
15+
exports[`cache should work with the "memory" cache: errors 1`] = `Array []`;
16+
17+
exports[`cache should work with the "memory" cache: errors 2`] = `Array []`;
18+
19+
exports[`cache should work with the "memory" cache: warnings 1`] = `Array []`;
20+
21+
exports[`cache should work with the "memory" cache: warnings 2`] = `Array []`;

‎test/__snapshots__/validate-options.test.js.snap

+22-17
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ exports[`validate options should throw an error on the "options" option with "{"
1414
exports[`validate options should throw an error on the "patterns" option with "" value 1`] = `
1515
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
1616
- options.patterns should be an array:
17-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
17+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
1818
`;
1919
2020
exports[`validate options should throw an error on the "patterns" option with "[""]" value 1`] = `
@@ -40,7 +40,7 @@ exports[`validate options should throw an error on the "patterns" option with "[
4040
exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":"string"}]" value 1`] = `
4141
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
4242
- options.patterns[0] should be one of these:
43-
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
43+
non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
4444
Details:
4545
* options.patterns[0].info should be one of these:
4646
object { … } | function
@@ -53,7 +53,7 @@ exports[`validate options should throw an error on the "patterns" option with "[
5353
exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":true}]" value 1`] = `
5454
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
5555
- options.patterns[0] should be one of these:
56-
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
56+
non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
5757
Details:
5858
* options.patterns[0].info should be one of these:
5959
object { … } | function
@@ -88,7 +88,7 @@ exports[`validate options should throw an error on the "patterns" option with "[
8888
exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","transform":true}]" value 1`] = `
8989
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
9090
- options.patterns[0] should be one of these:
91-
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
91+
non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
9292
Details:
9393
* options.patterns[0].transform should be one of these:
9494
function | object { transformer?, cache? }
@@ -98,6 +98,11 @@ exports[`validate options should throw an error on the "patterns" option with "[
9898
object { transformer?, cache? }"
9999
`;
100100
101+
exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","transformAll":true}]" value 1`] = `
102+
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
103+
- options.patterns[0].transformAll should be an instance of function."
104+
`;
105+
101106
exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":true}]" value 1`] = `
102107
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
103108
- options.patterns[0].context should be a string."
@@ -121,7 +126,7 @@ exports[`validate options should throw an error on the "patterns" option with "[
121126
exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":true,"context":"context"}]" value 1`] = `
122127
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
123128
- options.patterns[0] should be one of these:
124-
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
129+
non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
125130
Details:
126131
* options.patterns[0].to should be one of these:
127132
string | function
@@ -149,71 +154,71 @@ exports[`validate options should throw an error on the "patterns" option with "[
149154
exports[`validate options should throw an error on the "patterns" option with "{}" value 1`] = `
150155
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
151156
- options.patterns should be an array:
152-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
157+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
153158
`;
154159
155160
exports[`validate options should throw an error on the "patterns" option with "true" value 1`] = `
156161
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
157162
- options.patterns should be an array:
158-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
163+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
159164
`;
160165
161166
exports[`validate options should throw an error on the "patterns" option with "true" value 2`] = `
162167
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
163168
- options.patterns should be an array:
164-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
169+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
165170
`;
166171
167172
exports[`validate options should throw an error on the "patterns" option with "undefined" value 1`] = `
168173
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
169174
- options misses the property 'patterns'. Should be:
170-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
175+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
171176
`;
172177
173178
exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
174179
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
175180
- options misses the property 'patterns'. Should be:
176-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
181+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
177182
`;
178183
179184
exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
180185
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
181186
- options misses the property 'patterns'. Should be:
182-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
187+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
183188
`;
184189
185190
exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
186191
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
187192
- options misses the property 'patterns'. Should be:
188-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
193+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
189194
`;
190195
191196
exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
192197
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
193198
- options misses the property 'patterns'. Should be:
194-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
199+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
195200
`;
196201
197202
exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
198203
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
199204
- options misses the property 'patterns'. Should be:
200-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
205+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
201206
`;
202207
203208
exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
204209
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
205210
- options misses the property 'patterns'. Should be:
206-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
211+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
207212
`;
208213
209214
exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
210215
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
211216
- options misses the property 'patterns'. Should be:
212-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
217+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
213218
`;
214219
215220
exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
216221
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
217222
- options misses the property 'patterns'. Should be:
218-
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
223+
[non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
219224
`;

‎test/transformAll-option.test.js

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import CopyPlugin from "../src";
2+
3+
import { runEmit } from "./helpers/run";
4+
import { compile, getCompiler, readAssets } from "./helpers";
5+
6+
describe("transformAll option", () => {
7+
it('should be defined "assets"', (done) => {
8+
runEmit({
9+
expectedAssetKeys: ["file.txt"],
10+
patterns: [
11+
{
12+
from: "file.txt",
13+
to: "file.txt",
14+
transformAll(assets) {
15+
expect(assets).toBeDefined();
16+
17+
return "";
18+
},
19+
},
20+
],
21+
})
22+
.then(done)
23+
.catch(done);
24+
});
25+
26+
it("should transform files", (done) => {
27+
runEmit({
28+
expectedAssetKeys: ["file.txt"],
29+
expectedAssetContent: {
30+
"file.txt":
31+
"new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
32+
},
33+
patterns: [
34+
{
35+
from: "directory/**/*.txt",
36+
to: "file.txt",
37+
transformAll(assets) {
38+
const result = assets.reduce((accumulator, asset) => {
39+
const content = asset.data.toString() || asset.sourceFilename;
40+
// eslint-disable-next-line no-param-reassign
41+
accumulator = `${accumulator}${content}::`;
42+
return accumulator;
43+
}, "");
44+
45+
return result;
46+
},
47+
},
48+
],
49+
})
50+
.then(done)
51+
.catch(done);
52+
});
53+
54+
it("should transform files when async function used", (done) => {
55+
runEmit({
56+
expectedAssetKeys: ["file.txt"],
57+
expectedAssetContent: {
58+
"file.txt":
59+
"directory/directoryfile.txt::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
60+
},
61+
patterns: [
62+
{
63+
from: "directory/**/*.txt",
64+
to: "file.txt",
65+
async transformAll(assets) {
66+
const result = assets.reduce((accumulator, asset) => {
67+
// eslint-disable-next-line no-param-reassign
68+
accumulator = `${accumulator}${asset.sourceFilename}::`;
69+
return accumulator;
70+
}, "");
71+
72+
return result;
73+
},
74+
},
75+
],
76+
})
77+
.then(done)
78+
.catch(done);
79+
});
80+
81+
it("should transform files with force option enabled", (done) => {
82+
runEmit({
83+
expectedAssetKeys: ["file.txt"],
84+
expectedAssetContent: {
85+
"file.txt":
86+
"directory/directoryfile.txt::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
87+
},
88+
patterns: [
89+
{
90+
from: "file.txt",
91+
},
92+
{
93+
from: "directory/**/*.txt",
94+
to: "file.txt",
95+
transformAll(assets) {
96+
const result = assets.reduce((accumulator, asset) => {
97+
// eslint-disable-next-line no-param-reassign
98+
accumulator = `${accumulator}${asset.sourceFilename}::`;
99+
return accumulator;
100+
}, "");
101+
102+
return result;
103+
},
104+
force: true,
105+
},
106+
],
107+
})
108+
.then(done)
109+
.catch(done);
110+
});
111+
112+
it('should warn when "to" option is not defined', (done) => {
113+
runEmit({
114+
expectedAssetKeys: [],
115+
expectedErrors: [
116+
new Error(
117+
`Invalid "pattern.to" for the "pattern.from": "file.txt" and "pattern.transformAll" function. The "to" option must be specified.`
118+
),
119+
],
120+
patterns: [
121+
{
122+
from: "file.txt",
123+
transformAll() {
124+
return "";
125+
},
126+
},
127+
],
128+
})
129+
.then(done)
130+
.catch(done);
131+
});
132+
133+
it("should warn when function throw error", (done) => {
134+
runEmit({
135+
expectedAssetKeys: [],
136+
expectedErrors: [new Error("a failure happened")],
137+
patterns: [
138+
{
139+
from: "directory/**/*.txt",
140+
to: "file.txt",
141+
transformAll() {
142+
// eslint-disable-next-line no-throw-literal
143+
throw new Error("a failure happened");
144+
},
145+
},
146+
],
147+
})
148+
.then(done)
149+
.catch(done);
150+
});
151+
152+
it("should interpolate [fullhash] and [contenthash]", (done) => {
153+
runEmit({
154+
expectedAssetKeys: ["4333a40fa67dfaaaefc9-ac7f6fcb65ddfcc43b2c-file.txt"],
155+
expectedAssetContent: {
156+
"4333a40fa67dfaaaefc9-ac7f6fcb65ddfcc43b2c-file.txt":
157+
"::special::new::::::::::new::::::new::",
158+
},
159+
patterns: [
160+
{
161+
from: "**/*.txt",
162+
to: "[contenthash]-[fullhash]-file.txt",
163+
transformAll(assets) {
164+
const result = assets.reduce((accumulator, asset) => {
165+
// eslint-disable-next-line no-param-reassign
166+
accumulator = `${accumulator}${asset.data}::`;
167+
return accumulator;
168+
}, "");
169+
170+
return result;
171+
},
172+
},
173+
],
174+
})
175+
.then(done)
176+
.catch(done);
177+
});
178+
});
179+
180+
describe("cache", () => {
181+
it('should work with the "memory" cache', async () => {
182+
const compiler = getCompiler({});
183+
184+
new CopyPlugin({
185+
patterns: [
186+
{
187+
from: "directory/**/*.txt",
188+
to: "file.txt",
189+
transformAll(assets) {
190+
const result = assets.reduce((accumulator, asset) => {
191+
const content = asset.data.toString() || asset.sourceFilename;
192+
// eslint-disable-next-line no-param-reassign
193+
accumulator = `${accumulator}${content}::`;
194+
return accumulator;
195+
}, "");
196+
197+
return result;
198+
},
199+
},
200+
],
201+
}).apply(compiler);
202+
203+
const { stats } = await compile(compiler);
204+
205+
expect(stats.compilation.emittedAssets.size).toBe(2);
206+
expect(readAssets(compiler, stats)).toMatchSnapshot("assets");
207+
expect(stats.compilation.errors).toMatchSnapshot("errors");
208+
expect(stats.compilation.warnings).toMatchSnapshot("warnings");
209+
210+
await new Promise(async (resolve) => {
211+
const { stats: newStats } = await compile(compiler);
212+
213+
expect(newStats.compilation.emittedAssets.size).toBe(0);
214+
expect(readAssets(compiler, newStats)).toMatchSnapshot("assets");
215+
expect(newStats.compilation.errors).toMatchSnapshot("errors");
216+
expect(newStats.compilation.warnings).toMatchSnapshot("warnings");
217+
218+
resolve();
219+
});
220+
});
221+
});

‎test/validate-options.test.js

+16
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ describe("validate options", () => {
140140
priority: 5,
141141
},
142142
],
143+
[
144+
{
145+
from: "test.txt",
146+
to: "dir",
147+
context: "context",
148+
transformAll: ({ existingAsset }) => existingAsset.source.source(),
149+
},
150+
],
143151
],
144152
failure: [
145153
// eslint-disable-next-line no-undefined
@@ -270,6 +278,14 @@ describe("validate options", () => {
270278
priority: true,
271279
},
272280
],
281+
[
282+
{
283+
from: "test.txt",
284+
to: "dir",
285+
context: "context",
286+
transformAll: true,
287+
},
288+
],
273289
],
274290
},
275291
options: {

0 commit comments

Comments
 (0)
Please sign in to comment.