Skip to content

Commit

Permalink
feat: added transformAll option
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito committed Mar 19, 2021
1 parent 4ca7f80 commit 776aa89
Show file tree
Hide file tree
Showing 7 changed files with 410 additions and 34 deletions.
40 changes: 40 additions & 0 deletions README.md
Expand Up @@ -87,6 +87,7 @@ module.exports = {
| [`force`](#force) | `{Boolean}` | `false` | Overwrites files already in `compilation.assets` (usually added by other plugins/loaders). |
| [`priority`](#priority) | `{Number}` | `0` | Allows you to specify the copy priority. |
| [`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. |
| [`transformAll`](#transformAll) | `{Function}` | `undefined` | Allows you to modify the contents of multiple files and save the result to one file. |
| [`noErrorOnMissing`](#noerroronmissing) | `{Boolean}` | `false` | Doesn't generate an error on missing file(s). |
| [`info`](#info) | `{Object\|Function}` | `undefined` | Allows to add assets info. |

Expand Down Expand Up @@ -730,6 +731,45 @@ module.exports = {
};
```

#### `transformAll`

Type: `Function`
Default: `undefined`

Allows you to modify the contents of multiple files and save the result to one file.

> ℹ️ The `to` option must specify to a file. It is allowed to use only `[contenthash]` and `[fullhash]` template strings.
**webpack.config.js**

```js
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: "src/**/*.txt",
to: "dest/file.txt",
// The `assets` argument is an assets array for the pattern.from ("src/**/*.txt")
transformAll(assets) {
const result = assets.reduce((accumulator, asset) => {
// The asset content can be obtained from `asset.source` using `source` method.
// 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()`
const content = asset.source.source();

accumulator = `${accumulator}${content}\n`;
return accumulator;
}, "");

return result;
},
},
],
}),
],
};
```

### `noErrorOnMissing`

Type: `Boolean`
Expand Down
124 changes: 107 additions & 17 deletions src/index.js
Expand Up @@ -69,6 +69,27 @@ class CopyPlugin {
});
}

static getContentHash(compiler, compilation, source) {
const { outputOptions } = compilation;
const {
hashDigest,
hashDigestLength,
hashFunction,
hashSalt,
} = outputOptions;
const hash = compiler.webpack.util.createHash(hashFunction);

if (hashSalt) {
hash.update(hashSalt);
}

hash.update(source);

const fullContentHash = hash.digest(hashDigest);

return fullContentHash.slice(0, hashDigestLength);
}

static async runPattern(
compiler,
compilation,
Expand Down Expand Up @@ -338,6 +359,7 @@ class CopyPlugin {
filename,
force: pattern.force,
info,
toType,
};

// If this came from a glob or dir, add it to the file dependencies
Expand Down Expand Up @@ -526,23 +548,11 @@ class CopyPlugin {
`interpolating template '${filename}' for '${sourceFilename}'...`
);

const { outputOptions } = compilation;
const {
hashDigest,
hashDigestLength,
hashFunction,
hashSalt,
} = outputOptions;
const hash = compiler.webpack.util.createHash(hashFunction);

if (hashSalt) {
hash.update(hashSalt);
}

hash.update(result.source.source());

const fullContentHash = hash.digest(hashDigest);
const contentHash = fullContentHash.slice(0, hashDigestLength);
const contentHash = CopyPlugin.getContentHash(
compiler,
compilation,
result.source.source()
);
const ext = path.extname(result.sourceFilename);
const base = path.basename(result.sourceFilename);
const name = base.slice(0, base.length - ext.length);
Expand Down Expand Up @@ -634,6 +644,86 @@ class CopyPlugin {
}

if (assets && assets.length > 0) {
if (item.transformAll) {
assets.sort((a, b) =>
a.absoluteFilename > b.absoluteFilename
? 1
: a.absoluteFilename < b.absoluteFilename
? -1
: 0
);

const mergedEtag =
assets.length === 1
? cache.getLazyHashedEtag(assets[0].source.source())
: assets.reduce((accumulator, asset, ind) => {
// eslint-disable-next-line no-param-reassign
accumulator = cache.mergeEtags(
ind === 1
? cache.getLazyHashedEtag(
accumulator.source.source()
)
: accumulator,
cache.getLazyHashedEtag(asset.source.source())
);

return accumulator;
});

const cacheKeys = `transformAll|${serialize({
version,
transform: item.transformAll,
})}`;
const eTag = cache.getLazyHashedEtag(mergedEtag);
const cacheItem = cache.getItemCache(cacheKeys, eTag);
let transformedAsset = await cacheItem.getPromise();

if (!transformedAsset) {
const { RawSource } = compiler.webpack.sources;

transformedAsset = {};

try {
transformedAsset.source = new RawSource(
await item.transformAll(assets)
);
} catch (error) {
compilation.errors.push(error);

return;
}

let mergedFilename = assets[0].filename;

if (assets[0].toType === "template") {
const mergedContentHash = CopyPlugin.getContentHash(
compiler,
compilation,
transformedAsset.source.source()
);
const { contenthash: oldContentHash } = assets[0].info;
const regExp = new RegExp(oldContentHash, "gi");

mergedFilename = mergedFilename.replace(
regExp,
mergedContentHash
);

transformedAsset.info = {
contenthash: mergedContentHash,
fullhash: assets[0].info.fullhash,
};
}

transformedAsset.force = item.force;
transformedAsset.filename = mergedFilename;

await cacheItem.storePromise(transformedAsset);
}

assets = [transformedAsset];
}

const priority = item.priority || 0;

if (!assetMap.has(priority)) {
Expand Down
3 changes: 3 additions & 0 deletions src/options.json
Expand Up @@ -27,6 +27,9 @@
"filter": {
"instanceof": "Function"
},
"transformAll": {
"instanceof": "Function"
},
"toType": {
"enum": ["dir", "file", "template"]
},
Expand Down
21 changes: 21 additions & 0 deletions test/__snapshots__/transformAll-option.test.js.snap
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`cache should work with the "memory" cache: assets 1`] = `
Object {
"file.txt": "new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
}
`;

exports[`cache should work with the "memory" cache: assets 2`] = `
Object {
"file.txt": "new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::",
}
`;

exports[`cache should work with the "memory" cache: errors 1`] = `Array []`;

exports[`cache should work with the "memory" cache: errors 2`] = `Array []`;

exports[`cache should work with the "memory" cache: warnings 1`] = `Array []`;

exports[`cache should work with the "memory" cache: warnings 2`] = `Array []`;

0 comments on commit 776aa89

Please sign in to comment.