Skip to content

Commit dde71f0

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

File tree

7 files changed

+453
-35
lines changed

7 files changed

+453
-35
lines changed

README.md

Lines changed: 40 additions & 0 deletions
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.
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

Lines changed: 130 additions & 18 deletions
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

Lines changed: 3 additions & 0 deletions
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
},
Lines changed: 21 additions & 0 deletions
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 []`;

0 commit comments

Comments
 (0)