From dde71f01417b9291c7029a3876e043d76beb9e8d Mon Sep 17 00:00:00 2001 From: Alexey Lavinsky Date: Mon, 22 Mar 2021 18:25:41 +0300 Subject: [PATCH] feat: added the `transformAll` option (#596) --- README.md | 40 ++++ src/index.js | 148 ++++++++++-- src/options.json | 3 + .../transformAll-option.test.js.snap | 21 ++ .../validate-options.test.js.snap | 39 ++-- test/transformAll-option.test.js | 221 ++++++++++++++++++ test/validate-options.test.js | 16 ++ 7 files changed, 453 insertions(+), 35 deletions(-) create mode 100644 test/__snapshots__/transformAll-option.test.js.snap create mode 100644 test/transformAll-option.test.js diff --git a/README.md b/README.md index 7b4cc6d..44510fa 100644 --- a/README.md +++ b/README.md @@ -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. | @@ -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 be specified and point 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.data; + + accumulator = `${accumulator}${content}\n`; + return accumulator; + }, ""); + + return result; + }, + }, + ], + }), + ], +}; +``` + ### `noErrorOnMissing` Type: `Boolean` diff --git a/src/index.js b/src/index.js index ad6217a..03ec33a 100644 --- a/src/index.js +++ b/src/index.js @@ -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, @@ -456,7 +477,7 @@ class CopyPlugin { if (transform.transformer) { logger.log(`transforming content for '${absoluteFilename}'...`); - const buffer = result.source.source(); + const buffer = result.source.buffer(); if (transform.cache) { const defaultCacheKeys = { @@ -526,23 +547,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.buffer() + ); const ext = path.extname(result.sourceFilename); const base = path.basename(result.sourceFilename); const name = base.slice(0, base.length - ext.length); @@ -634,6 +643,109 @@ class CopyPlugin { } if (assets && assets.length > 0) { + if (item.transformAll) { + if (typeof item.to === "undefined") { + compilation.errors.push( + new Error( + `Invalid "pattern.to" for the "pattern.from": "${item.from}" and "pattern.transformAll" function. The "to" option must be specified.` + ) + ); + + return; + } + + 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.buffer()) + : assets.reduce((accumulator, asset, i) => { + // eslint-disable-next-line no-param-reassign + accumulator = cache.mergeEtags( + i === 1 + ? cache.getLazyHashedEtag( + accumulator.source.buffer() + ) + : accumulator, + cache.getLazyHashedEtag(asset.source.buffer()) + ); + + return accumulator; + }); + + const cacheKeys = `transformAll|${serialize({ + version, + from: item.from, + to: item.to, + transformAll: item.transformAll, + })}`; + const eTag = cache.getLazyHashedEtag(mergedEtag); + const cacheItem = cache.getItemCache(cacheKeys, eTag); + let transformedAsset = await cacheItem.getPromise(); + + if (!transformedAsset) { + transformedAsset = { filename: item.to }; + + try { + transformedAsset.data = await item.transformAll( + assets.map((asset) => { + return { + data: asset.source.buffer(), + sourceFilename: asset.sourceFilename, + absoluteFilename: asset.absoluteFilename, + }; + }) + ); + } catch (error) { + compilation.errors.push(error); + + return; + } + + if (template.test(item.to)) { + const contentHash = CopyPlugin.getContentHash( + compiler, + compilation, + transformedAsset.data + ); + + const { + path: interpolatedFilename, + info: assetInfo, + } = compilation.getPathWithInfo( + normalizePath(item.to), + { + contentHash, + chunk: { + hash: contentHash, + contentHash, + }, + } + ); + + transformedAsset.filename = interpolatedFilename; + transformedAsset.info = assetInfo; + } + + const { RawSource } = compiler.webpack.sources; + + transformedAsset.source = new RawSource( + transformedAsset.data + ); + transformedAsset.force = item.force; + + await cacheItem.storePromise(transformedAsset); + } + + assets = [transformedAsset]; + } + const priority = item.priority || 0; if (!assetMap.has(priority)) { diff --git a/src/options.json b/src/options.json index 725ff76..e93ee01 100644 --- a/src/options.json +++ b/src/options.json @@ -27,6 +27,9 @@ "filter": { "instanceof": "Function" }, + "transformAll": { + "instanceof": "Function" + }, "toType": { "enum": ["dir", "file", "template"] }, diff --git a/test/__snapshots__/transformAll-option.test.js.snap b/test/__snapshots__/transformAll-option.test.js.snap new file mode 100644 index 0000000..0ead4ab --- /dev/null +++ b/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 []`; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index fd384cb..e0e007d 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -14,7 +14,7 @@ exports[`validate options should throw an error on the "options" option with "{" exports[`validate options should throw an error on the "patterns" option with "" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns should be an array: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; 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 "[ exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":"string"}]" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns[0] should be one of these: - non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? } + non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? } Details: * options.patterns[0].info should be one of these: object { … } | function @@ -53,7 +53,7 @@ exports[`validate options should throw an error on the "patterns" option with "[ exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":true}]" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns[0] should be one of these: - non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? } + non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? } Details: * options.patterns[0].info should be one of these: object { … } | function @@ -88,7 +88,7 @@ exports[`validate options should throw an error on the "patterns" option with "[ exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","transform":true}]" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns[0] should be one of these: - non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? } + non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? } Details: * options.patterns[0].transform should be one of these: function | object { transformer?, cache? } @@ -98,6 +98,11 @@ exports[`validate options should throw an error on the "patterns" option with "[ object { transformer?, cache? }" `; +exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":"context","transformAll":true}]" value 1`] = ` +"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. + - options.patterns[0].transformAll should be an instance of function." +`; + exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","context":true}]" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns[0].context should be a string." @@ -121,7 +126,7 @@ exports[`validate options should throw an error on the "patterns" option with "[ exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":true,"context":"context"}]" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns[0] should be one of these: - non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? } + non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? } Details: * options.patterns[0].to should be one of these: string | function @@ -149,71 +154,71 @@ exports[`validate options should throw an error on the "patterns" option with "[ exports[`validate options should throw an error on the "patterns" option with "{}" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns should be an array: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "patterns" option with "true" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns should be an array: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "patterns" option with "true" value 2`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options.patterns should be an array: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "patterns" option with "undefined" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema. - options misses the property 'patterns'. Should be: - [non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" + [non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)" `; diff --git a/test/transformAll-option.test.js b/test/transformAll-option.test.js new file mode 100644 index 0000000..deca8aa --- /dev/null +++ b/test/transformAll-option.test.js @@ -0,0 +1,221 @@ +import CopyPlugin from "../src"; + +import { runEmit } from "./helpers/run"; +import { compile, getCompiler, readAssets } from "./helpers"; + +describe("transformAll option", () => { + it('should be defined "assets"', (done) => { + runEmit({ + expectedAssetKeys: ["file.txt"], + patterns: [ + { + from: "file.txt", + to: "file.txt", + transformAll(assets) { + expect(assets).toBeDefined(); + + return ""; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it("should transform files", (done) => { + runEmit({ + expectedAssetKeys: ["file.txt"], + expectedAssetContent: { + "file.txt": + "new::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::", + }, + patterns: [ + { + from: "directory/**/*.txt", + to: "file.txt", + transformAll(assets) { + const result = assets.reduce((accumulator, asset) => { + const content = asset.data.toString() || asset.sourceFilename; + // eslint-disable-next-line no-param-reassign + accumulator = `${accumulator}${content}::`; + return accumulator; + }, ""); + + return result; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it("should transform files when async function used", (done) => { + runEmit({ + expectedAssetKeys: ["file.txt"], + expectedAssetContent: { + "file.txt": + "directory/directoryfile.txt::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::", + }, + patterns: [ + { + from: "directory/**/*.txt", + to: "file.txt", + async transformAll(assets) { + const result = assets.reduce((accumulator, asset) => { + // eslint-disable-next-line no-param-reassign + accumulator = `${accumulator}${asset.sourceFilename}::`; + return accumulator; + }, ""); + + return result; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it("should transform files with force option enabled", (done) => { + runEmit({ + expectedAssetKeys: ["file.txt"], + expectedAssetContent: { + "file.txt": + "directory/directoryfile.txt::directory/nested/deep-nested/deepnested.txt::directory/nested/nestedfile.txt::", + }, + patterns: [ + { + from: "file.txt", + }, + { + from: "directory/**/*.txt", + to: "file.txt", + transformAll(assets) { + const result = assets.reduce((accumulator, asset) => { + // eslint-disable-next-line no-param-reassign + accumulator = `${accumulator}${asset.sourceFilename}::`; + return accumulator; + }, ""); + + return result; + }, + force: true, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warn when "to" option is not defined', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedErrors: [ + new Error( + `Invalid "pattern.to" for the "pattern.from": "file.txt" and "pattern.transformAll" function. The "to" option must be specified.` + ), + ], + patterns: [ + { + from: "file.txt", + transformAll() { + return ""; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it("should warn when function throw error", (done) => { + runEmit({ + expectedAssetKeys: [], + expectedErrors: [new Error("a failure happened")], + patterns: [ + { + from: "directory/**/*.txt", + to: "file.txt", + transformAll() { + // eslint-disable-next-line no-throw-literal + throw new Error("a failure happened"); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it("should interpolate [fullhash] and [contenthash]", (done) => { + runEmit({ + expectedAssetKeys: ["4333a40fa67dfaaaefc9-ac7f6fcb65ddfcc43b2c-file.txt"], + expectedAssetContent: { + "4333a40fa67dfaaaefc9-ac7f6fcb65ddfcc43b2c-file.txt": + "::special::new::::::::::new::::::new::", + }, + patterns: [ + { + from: "**/*.txt", + to: "[contenthash]-[fullhash]-file.txt", + transformAll(assets) { + const result = assets.reduce((accumulator, asset) => { + // eslint-disable-next-line no-param-reassign + accumulator = `${accumulator}${asset.data}::`; + return accumulator; + }, ""); + + return result; + }, + }, + ], + }) + .then(done) + .catch(done); + }); +}); + +describe("cache", () => { + it('should work with the "memory" cache', async () => { + const compiler = getCompiler({}); + + new CopyPlugin({ + patterns: [ + { + from: "directory/**/*.txt", + to: "file.txt", + transformAll(assets) { + const result = assets.reduce((accumulator, asset) => { + const content = asset.data.toString() || asset.sourceFilename; + // eslint-disable-next-line no-param-reassign + accumulator = `${accumulator}${content}::`; + return accumulator; + }, ""); + + return result; + }, + }, + ], + }).apply(compiler); + + const { stats } = await compile(compiler); + + expect(stats.compilation.emittedAssets.size).toBe(2); + expect(readAssets(compiler, stats)).toMatchSnapshot("assets"); + expect(stats.compilation.errors).toMatchSnapshot("errors"); + expect(stats.compilation.warnings).toMatchSnapshot("warnings"); + + await new Promise(async (resolve) => { + const { stats: newStats } = await compile(compiler); + + expect(newStats.compilation.emittedAssets.size).toBe(0); + expect(readAssets(compiler, newStats)).toMatchSnapshot("assets"); + expect(newStats.compilation.errors).toMatchSnapshot("errors"); + expect(newStats.compilation.warnings).toMatchSnapshot("warnings"); + + resolve(); + }); + }); +}); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 26ffbdc..607a869 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -140,6 +140,14 @@ describe("validate options", () => { priority: 5, }, ], + [ + { + from: "test.txt", + to: "dir", + context: "context", + transformAll: ({ existingAsset }) => existingAsset.source.source(), + }, + ], ], failure: [ // eslint-disable-next-line no-undefined @@ -270,6 +278,14 @@ describe("validate options", () => { priority: true, }, ], + [ + { + from: "test.txt", + to: "dir", + context: "context", + transformAll: true, + }, + ], ], }, options: {