Skip to content

Commit

Permalink
feat: added priority option (#590)
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito committed Mar 4, 2021
1 parent a9b06a6 commit ea610bc
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 22 deletions.
36 changes: 36 additions & 0 deletions README.md
Expand Up @@ -85,6 +85,7 @@ module.exports = {
| [`filter`](#filter) | `{Function}` | `undefined` | Allows to filter copied assets. |
| [`toType`](#totype) | `{String}` | `undefined` | Determinate what is `to` option - directory, file or template. |
| [`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. |
| [`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 @@ -462,6 +463,41 @@ module.exports = {
};
```

#### `priority`

Type: `Number`
Default: `0`

Allows to specify the priority of copying files with the same destination name.
Files for patterns with higher priority will be copied later.
To overwrite files, the [`force`](#force) option must be enabled.

**webpack.config.js**

```js
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
// Сopied second and will overwrite "dir_2/file.txt"
{
from: "dir_1/file.txt",
to: "newfile.txt",
force: true,
priority: 10,
},
// Сopied first
{
from: "dir_2/file.txt",
to: "newfile.txt",
priority: 5,
},
],
}),
],
};
```

#### `transform`

Type: `Function|Object`
Expand Down
32 changes: 27 additions & 5 deletions src/index.js
Expand Up @@ -70,6 +70,7 @@ class CopyPlugin {
}

static async runPattern(
assetMap,
compiler,
compilation,
logger,
Expand Down Expand Up @@ -313,7 +314,13 @@ class CopyPlugin {
path.relative(compiler.context, absoluteFilename)
);

return { absoluteFilename, sourceFilename, filename, toType };
return {
absoluteFilename,
sourceFilename,
filename,
toType,
priority: pattern.priority || 0,
};
})
);

Expand All @@ -322,7 +329,13 @@ class CopyPlugin {
try {
assets = await Promise.all(
files.map(async (file) => {
const { absoluteFilename, sourceFilename, filename, toType } = file;
const {
absoluteFilename,
sourceFilename,
filename,
toType,
priority,
} = file;
const info =
typeof pattern.info === "function"
? pattern.info(file) || {}
Expand Down Expand Up @@ -578,6 +591,12 @@ class CopyPlugin {
result.filename = normalizePath(result.filename);
}

if (!assetMap.has(priority)) {
assetMap.set(priority, []);
}

assetMap.get(priority).push(result);

// eslint-disable-next-line consistent-return
return result;
})
Expand Down Expand Up @@ -612,13 +631,14 @@ class CopyPlugin {
async (unusedAssets, callback) => {
logger.log("starting to add additional assets...");

let assets;
const assetMap = new Map();

try {
assets = await Promise.all(
await Promise.all(
this.patterns.map((item, index) =>
limit(async () =>
CopyPlugin.runPattern(
assetMap,
compiler,
compilation,
logger,
Expand All @@ -637,10 +657,12 @@ class CopyPlugin {
return;
}

const assets = [...assetMap.entries()].sort((a, b) => a[0] - b[0]);

// Avoid writing assets inside `p-limit`, because it creates concurrency.
// It could potentially lead to an error - 'Multiple assets emit different content to the same filename'
assets
.reduce((acc, val) => acc.concat(val), [])
.reduce((acc, val) => acc.concat(val[1]), [])
.filter(Boolean)
.forEach((asset) => {
const {
Expand Down
3 changes: 3 additions & 0 deletions src/options.json
Expand Up @@ -33,6 +33,9 @@
"force": {
"type": "boolean"
},
"priority": {
"type": "number"
},
"info": {
"anyOf": [
{
Expand Down
49 changes: 32 additions & 17 deletions test/__snapshots__/validate-options.test.js.snap
Expand Up @@ -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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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`] = `
Expand All @@ -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?, info?, transform?, transformPath?, noErrorOnMissing? }
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
Details:
* options.patterns[0].info should be one of these:
object { … } | function
Expand All @@ -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?, info?, transform?, transformPath?, noErrorOnMissing? }
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
Details:
* options.patterns[0].info should be one of these:
object { … } | function
Expand Down Expand Up @@ -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?, info?, transform?, transformPath?, noErrorOnMissing? }
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
Details:
* options.patterns[0].transform should be one of these:
function | object { transformer?, cache? }
Expand All @@ -103,10 +103,25 @@ exports[`validate options should throw an error on the "patterns" option with "[
- options.patterns[0].context should be a string."
`;
exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","priority":"5"}]" value 1`] = `
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
- options.patterns[0].priority should be a number."
`;
exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir","priority":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].priority should be a number."
`;
exports[`validate options should throw an error on the "patterns" option with "[{"from":"test.txt","to":"dir"}]" value 1`] = `
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
- options.patterns[0].priority should be a number."
`;
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?, info?, transform?, transformPath?, noErrorOnMissing? }
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
Details:
* options.patterns[0].to should be one of these:
string | function
Expand Down Expand Up @@ -134,71 +149,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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, 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?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
[non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }, ...] (should not have fewer than 1 item)"
`;
57 changes: 57 additions & 0 deletions test/priority-option.test.js
@@ -0,0 +1,57 @@
import { run } from "./helpers/run";

describe("priority option", () => {
it("should copy without specifying priority option", (done) => {
run({
expectedAssetKeys: [],
patterns: [
{
from: "dir (86)/file.txt",
to: "newfile.txt",
force: true,
},
{
from: "file.txt",
to: "newfile.txt",
force: true,
},
],
})
.then(({ stats }) => {
const { info } = stats.compilation.getAsset("newfile.txt");

expect(info.sourceFilename).toEqual("file.txt");

done();
})
.catch(done);
});

it("should copy with specifying priority option", (done) => {
run({
expectedAssetKeys: [],
patterns: [
{
from: "dir (86)/file.txt",
to: "newfile.txt",
force: true,
priority: 10,
},
{
from: "file.txt",
to: "newfile.txt",
force: true,
priority: 5,
},
],
})
.then(({ stats }) => {
const { info } = stats.compilation.getAsset("newfile.txt");

expect(info.sourceFilename).toEqual("dir (86)/file.txt");

done();
})
.catch(done);
});
});

0 comments on commit ea610bc

Please sign in to comment.