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: implement support for array values in from property. Closes #631 #645

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
31 changes: 28 additions & 3 deletions README.md
Expand Up @@ -83,7 +83,7 @@ module.exports = {

| Name | Type | Default | Description |
| :-------------------------------------: | :------------------: | :---------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`from`](#from) | `{String}` | `undefined` | Glob or path from where we copy files. |
| [`from`](#from) | `{String\|Array}` | `undefined` | Glob or path from where we copy files, or array of such values. |
| [`to`](#to) | `{String\|Function}` | `compiler.options.output` | Output path. |
| [`context`](#context) | `{String}` | `options.context \|\| compiler.options.context` | A path that determines how to interpret the `from` path. |
| [`globOptions`](#globoptions) | `{Object}` | `undefined` | [Options][glob-options] passed to the glob pattern matching library including `ignore` option. |
Expand All @@ -98,12 +98,12 @@ module.exports = {

#### `from`

Type: `String`
Type: `String or Array of strings`
Default: `undefined`

Glob or path from where we copy files.
Globs accept [fast-glob pattern-syntax](https://github.com/mrmlnc/fast-glob#pattern-syntax).
Glob can only be a `string`.
Glob can only be a non-empty `string` or non-empty array of non-empty strings.

> ⚠️ Don't use directly `\\` in `from` option if it is a `glob` (i.e `path\to\file.ext`) option because on UNIX the backslash is a valid character inside a path component, i.e., it's not a separator.
> On Windows, the forward slash and the backward slash are both separators.
Expand Down Expand Up @@ -177,6 +177,31 @@ module.exports = {
The `context` behaves differently depending on what the `from` is (`glob`, `file` or `dir`).
More [`examples`](#examples)

##### Using arrays in `from`

When `from` is specified as an array, it behaves the same as if all array elements were processed separately
using shared options, such as `transform`. The only exception is `transformAll` which receives assets from processed
array elements in a single call, thus allowing to process group of specific files at once.

```js
module.exports = {
plugins: [
new CopyPlugin({
from: ["file.txt", "directory/directoryfile.txt"],
to: "file.txt",
transformAll(assets) {
const result = assets.sort().reduce((accumulator, asset) => {
const content = asset.sourceFilename;
accumulator = `${accumulator}${content}::`;
return accumulator;
}, "");
return result;
},
}),
],
};
```

#### `to`

Type: `String|Function`
Expand Down
44 changes: 35 additions & 9 deletions src/index.js
Expand Up @@ -633,15 +633,41 @@ class CopyPlugin {
let assets;

try {
assets = await CopyPlugin.runPattern(
globby,
compiler,
compilation,
logger,
cache,
item,
index
);
if (item.from instanceof Array) {
if (!item.from.every((from) => typeof from === "string")) {
compilation.errors.push(
new Error(
`Invalid "pattern.from": ${item.from}, every element should be a string"`
)
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid it, you validate it in schema already


assets = [].concat(
...(await Promise.all(
item.from.map(async (from) =>
CopyPlugin.runPattern(
globby,
compiler,
compilation,
logger,
cache,
{ ...item, from },
index
)
)
))
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not pass array to globby? They support it https://github.com/sindresorhus/globby#usage

} else {
assets = await CopyPlugin.runPattern(
globby,
compiler,
compilation,
logger,
cache,
item,
index
);
}
} catch (error) {
compilation.errors.push(error);

Expand Down
26 changes: 19 additions & 7 deletions src/options.json
Expand Up @@ -5,13 +5,25 @@
"additionalProperties": false,
"properties": {
"from": {
"type": "string",
"description": "Glob or path from where we copy files.",
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#from",
"minLength": 1
"oneOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1
}
],
"description": "Glob or path from where we copy files, or array of paths or globs.",
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#from"
},
"to": {
"anyOf": [
"oneOf": [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it was changed?

{
"type": "string"
},
Expand Down Expand Up @@ -58,7 +70,7 @@
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#priority"
},
"info": {
"anyOf": [
"oneOf": [
{
"type": "object"
},
Expand All @@ -72,7 +84,7 @@
"transform": {
"description": "Allows to modify the file contents.",
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#transform",
"anyOf": [
"oneOf": [
{
"instanceof": "Function"
},
Expand Down
46 changes: 35 additions & 11 deletions test/__snapshots__/validate-options.test.js.snap
Expand Up @@ -38,9 +38,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":"","to":"dir","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].from should be a non-empty string.
-> Glob or path from where we copy files.
-> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from"
- options.patterns[0].from should be a non-empty string."
`;

exports[`validate options should throw an error on the "patterns" option with "[{"from":"dir","info":"string"}]" value 1`] = `
Expand Down Expand Up @@ -165,25 +163,51 @@ exports[`validate options should throw an error on the "patterns" option with "[
* options.patterns[0].to should be an instance of function."
`;

exports[`validate options should throw an error on the "patterns" option with "[{"from":["test1.txt",1],"to":"dir","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].from[1] should be a non-empty string."
`;

exports[`validate options should throw an error on the "patterns" option with "[{"from":[]}]" value 1`] = `
"Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.
- options.patterns[0].from should be a non-empty array."
`;

exports[`validate options should throw an error on the "patterns" option with "[{"from":{"glob":"**/*","dot":false},"to":"dir","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].from should be a non-empty string.
-> Glob or path from where we copy files.
-> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from"
- options.patterns[0] should be one of these:
non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
Details:
* options.patterns[0].from should be one of these:
non-empty string | [non-empty string, ...] (should not have fewer than 1 item)
-> Glob or path from where we copy files, or array of paths or globs.
-> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from
Details:
* options.patterns[0].from should be a non-empty string.
* options.patterns[0].from should be an array:
[non-empty string, ...] (should not have fewer than 1 item)"
`;

exports[`validate options should throw an error on the "patterns" option with "[{"from":true,"to":"dir","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].from should be a non-empty string.
-> Glob or path from where we copy files.
-> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from"
- options.patterns[0] should be one of these:
non-empty string | object { from, to?, context?, globOptions?, filter?, transformAll?, toType?, force?, priority?, info?, transform?, transformPath?, noErrorOnMissing? }
Details:
* options.patterns[0].from should be one of these:
non-empty string | [non-empty string, ...] (should not have fewer than 1 item)
-> Glob or path from where we copy files, or array of paths or globs.
-> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from
Details:
* options.patterns[0].from should be a non-empty string.
* options.patterns[0].from should be an array:
[non-empty string, ...] (should not have fewer than 1 item)"
`;

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[0] misses the property 'from'. Should be:
non-empty string
-> Glob or path from where we copy files.
non-empty string | [non-empty string, ...] (should not have fewer than 1 item)
-> Glob or path from where we copy files, or array of paths or globs.
-> Read more at https://github.com/webpack-contrib/copy-webpack-plugin#from"
`;

Expand Down
33 changes: 33 additions & 0 deletions test/from-option.test.js
Expand Up @@ -21,6 +21,18 @@ describe("from option", () => {
.then(done)
.catch(done);
});
it("should copy an array of files", (done) => {
runEmit({
expectedAssetKeys: ["file.txt", "directoryfile.txt"],
patterns: [
{
from: ["file.txt", "directory/directoryfile.txt"],
},
],
})
.then(done)
.catch(done);
});

it('should copy a file when "from" an absolute path', (done) => {
runEmit({
Expand Down Expand Up @@ -154,6 +166,27 @@ describe("from option", () => {
.catch(done);
});

it('should copy files when "from" is an array of directories', (done) => {
runEmit({
expectedAssetKeys: [
"file.txt",
"nesteddir/deepnesteddir/deepnesteddir.txt",
"nesteddir/nestedfile.txt",
".dottedfile",
"directoryfile.txt",
"nested/deep-nested/deepnested.txt",
"nested/nestedfile.txt",
],
patterns: [
{
from: ["dir (86)", "directory"],
},
],
})
.then(done)
.catch(done);
});

it('should copy files when "from" is relative path to context', (done) => {
runEmit({
expectedAssetKeys: [
Expand Down
24 changes: 24 additions & 0 deletions test/transform-option.test.js
Expand Up @@ -29,6 +29,30 @@ describe("transform option", () => {
.catch(done);
});

it('should transform files when "from" is an array of files', (done) => {
runEmit({
expectedAssetKeys: ["file.txt", "directoryfile.txt"],
expectedAssetContent: {
"file.txt": "newchanged",
"directoryfile.txt": "newchanged",
},
patterns: [
{
from: ["file.txt", "directory/directoryfile.txt"],
transform: {
transformer(content, absoluteFrom) {
expect(absoluteFrom.includes(FIXTURES_DIR)).toBe(true);

return `${content}changed`;
},
},
},
],
})
.then(done)
.catch(done);
});

it('should transform target path of every when "from" is a directory', (done) => {
runEmit({
expectedAssetKeys: [
Expand Down
27 changes: 27 additions & 0 deletions test/transformAll-option.test.js
Expand Up @@ -51,6 +51,33 @@ describe("transformAll option", () => {
.catch(done);
});

it('should transform files when when "from" is an array of files', (done) => {
runEmit({
expectedAssetKeys: ["file.txt"],
expectedAssetContent: {
"file.txt": "directory/directoryfile.txt::file.txt::",
},
patterns: [
{
from: ["file.txt", "directory/directoryfile.txt"],
to: "file.txt",
transformAll(assets) {
const result = assets.sort().reduce((accumulator, asset) => {
const content = 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"],
Expand Down
21 changes: 21 additions & 0 deletions test/validate-options.test.js
Expand Up @@ -59,6 +59,14 @@ describe("validate options", () => {
transform: () => {},
},
],
[
{
from: ["test1.txt", "test2.txt"],
to: "dir",
context: "context",
transform: () => {},
},
],
[
{
from: "test.txt",
Expand Down Expand Up @@ -165,6 +173,11 @@ describe("validate options", () => {
info: "string",
},
],
[
{
from: [],
},
],
[
{
from: "dir",
Expand Down Expand Up @@ -233,6 +246,14 @@ describe("validate options", () => {
transform: true,
},
],
[
{
from: ["test1.txt", 1],
to: "dir",
context: "context",
transform: () => {},
},
],
[
{
from: {
Expand Down