Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: added priority option #590

Merged
merged 1 commit into from Mar 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
});
});