Skip to content

Commit

Permalink
feat: added type Function for the to option (#563)
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito authored Dec 7, 2020
1 parent 7167645 commit 9bc5416
Show file tree
Hide file tree
Showing 8 changed files with 481 additions and 49 deletions.
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ module.exports = {
| Name | Type | Default | Description |
| :-------------------------------------: | :-------------------------: | :---------------------------------------------: | :---------------------------------------------------------------------------------------------------- |
| [`from`](#from) | `{String}` | `undefined` | Glob or path from where we сopy files. |
| [`to`](#to) | `{String}` | `compiler.options.output` | Output path. |
| [`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. |
| [`filter`](#filter) | `{Function}` | `undefined` | Allows to filter copied assets. |
Expand Down Expand Up @@ -174,9 +174,11 @@ More [`examples`](#examples)

#### `to`

Type: `String`
Type: `String|Function`
Default: `compiler.options.output`

##### String

Output path.

> ⚠️ Don't use directly `\\` in `to` (i.e `path\to\dest`) option because on UNIX the backslash is a valid character inside a path component, i.e., it's not a separator.
Expand Down Expand Up @@ -208,6 +210,53 @@ module.exports = {
};
```

##### Function

Allows to modify the writing path.

> ⚠️ Don't return directly `\\` in `to` (i.e `path\to\newFile`) 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.
> Instead please use `/` or `path` methods.
**webpack.config.js**

```js
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: "src/*.png",
to({ context, absoluteFilename }) {
return "dest/newPath";
},
},
],
}),
],
};
```

**webpack.config.js**

```js
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: "src/*.png",
to: "dest/",
to({ context, absoluteFilename }) {
return Promise.resolve("dest/newPath");
},
},
],
}),
],
};
```

#### `context`

Type: `String`
Expand Down
95 changes: 53 additions & 42 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,6 @@ class CopyPlugin {

pattern.fromOrigin = pattern.from;
pattern.from = path.normalize(pattern.from);
pattern.to = path.normalize(
typeof pattern.to !== "undefined" ? pattern.to : ""
);
pattern.compilerContext = compiler.context;
pattern.context = path.normalize(
typeof pattern.context !== "undefined"
Expand All @@ -115,26 +112,9 @@ class CopyPlugin {
);

logger.log(
`starting to process a pattern from '${pattern.from}' using '${pattern.context}' context to '${pattern.to}'...`
`starting to process a pattern from '${pattern.from}' using '${pattern.context}' context`
);

const isToDirectory =
path.extname(pattern.to) === "" || pattern.to.slice(-1) === path.sep;

switch (true) {
// if toType already exists
case !!pattern.toType:
break;
case template.test(pattern.to):
pattern.toType = "template";
break;
case isToDirectory:
pattern.toType = "dir";
break;
default:
pattern.toType = "file";
}

if (path.isAbsolute(pattern.from)) {
pattern.absoluteFrom = pattern.from;
} else {
Expand Down Expand Up @@ -310,33 +290,64 @@ class CopyPlugin {
return;
}

const files = filteredPaths.map((item) => {
const from = item.path;
const files = await Promise.all(
filteredPaths.map(async (item) => {
const from = item.path;

logger.debug(`found '${from}'`);
logger.debug(`found '${from}'`);

// `globby`/`fast-glob` return the relative path when the path contains special characters on windows
const absoluteFilename = path.resolve(pattern.context, from);
const relativeFrom = pattern.flatten
? path.basename(absoluteFilename)
: path.relative(pattern.context, absoluteFilename);
let filename =
pattern.toType === "dir"
? path.join(pattern.to, relativeFrom)
: pattern.to;
// `globby`/`fast-glob` return the relative path when the path contains special characters on windows
const absoluteFilename = path.resolve(pattern.context, from);

if (path.isAbsolute(filename)) {
filename = path.relative(compiler.options.output.path, filename);
}
pattern.to =
typeof pattern.to !== "function"
? path.normalize(
typeof pattern.to !== "undefined" ? pattern.to : ""
)
: await pattern.to({ context: pattern.context, absoluteFilename });

const isToDirectory =
path.extname(pattern.to) === "" || pattern.to.slice(-1) === path.sep;

switch (true) {
// if toType already exists
case !!pattern.toType:
break;
case template.test(pattern.to):
pattern.toType = "template";
break;
case isToDirectory:
pattern.toType = "dir";
break;
default:
pattern.toType = "file";
}

logger.log(`determined that '${from}' should write to '${filename}'`);
logger.log(
`'to' option '${pattern.to}' determinated as '${pattern.toType}'`
);

const sourceFilename = normalizePath(
path.relative(pattern.compilerContext, absoluteFilename)
);
const relativeFrom = pattern.flatten
? path.basename(absoluteFilename)
: path.relative(pattern.context, absoluteFilename);
let filename =
pattern.toType === "dir"
? path.join(pattern.to, relativeFrom)
: pattern.to;

return { absoluteFilename, sourceFilename, filename };
});
if (path.isAbsolute(filename)) {
filename = path.relative(compiler.options.output.path, filename);
}

logger.log(`determined that '${from}' should write to '${filename}'`);

const sourceFilename = normalizePath(
path.relative(pattern.compilerContext, absoluteFilename)
);

return { absoluteFilename, sourceFilename, filename };
})
);

let assets;

Expand Down
9 changes: 8 additions & 1 deletion src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
"minLength": 1
},
"to": {
"type": "string"
"anyOf": [
{
"type": "string"
},
{
"instanceof": "Function"
}
]
},
"context": {
"type": "string"
Expand Down
41 changes: 41 additions & 0 deletions test/CopyPlugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1246,5 +1246,46 @@ describe("CopyPlugin", () => {
.then(done)
.catch(done);
});

it("should logging when 'to' is a function", (done) => {
const expectedAssetKeys = ["newFile.txt"];

run({
patterns: [
{
from: "file.txt",
to() {
return "newFile.txt";
},
},
],
})
.then(({ compiler, stats }) => {
const root = path.resolve(__dirname).replace(/\\/g, "/");
const logs = stats.compilation.logging
.get("copy-webpack-plugin")
.map((entry) =>
entry.args[0].replace(/\\/g, "/").split(root).join(".")
)
// TODO remove after drop webpack@4
.filter(
(item) =>
!item.startsWith("created snapshot") &&
!item.startsWith("creating snapshot") &&
!item.startsWith("getting cache") &&
!item.startsWith("missed cache") &&
!item.startsWith("stored cache") &&
!item.startsWith("storing cache")
)
.sort();

expect(
Array.from(Object.keys(readAssets(compiler, stats))).sort()
).toEqual(expectedAssetKeys);
expect({ logs }).toMatchSnapshot("logs");
})
.then(done)
.catch(done);
});
});
});
36 changes: 33 additions & 3 deletions test/__snapshots__/CopyPlugin.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ exports[`CopyPlugin cache should work with the "transform" option: warnings 2`]
exports[`CopyPlugin logging should logging when "from" is a directory: logs 1`] = `
Object {
"logs": Array [
"'to' option '.' determinated as 'dir'",
"'to' option '.' determinated as 'dir'",
"'to' option '.' determinated as 'dir'",
"'to' option '.' determinated as 'dir'",
"added './fixtures/directory' as a context dependency",
"added './fixtures/directory/.dottedfile' as a file dependency",
"added './fixtures/directory/directoryfile.txt' as a file dependency",
Expand Down Expand Up @@ -202,7 +206,7 @@ Object {
"reading './fixtures/directory/nested/deep-nested/deepnested.txt'...",
"reading './fixtures/directory/nested/nestedfile.txt'...",
"starting to add additional assets...",
"starting to process a pattern from 'directory' using './fixtures' context to '.'...",
"starting to process a pattern from 'directory' using './fixtures' context",
"writing '.dottedfile' from './fixtures/directory/.dottedfile' to compilation assets...",
"writing 'directoryfile.txt' from './fixtures/directory/directoryfile.txt' to compilation assets...",
"writing 'nested/deep-nested/deepnested.txt' from './fixtures/directory/nested/deep-nested/deepnested.txt' to compilation assets...",
Expand All @@ -218,6 +222,7 @@ Object {
exports[`CopyPlugin logging should logging when "from" is a file: logs 1`] = `
Object {
"logs": Array [
"'to' option '.' determinated as 'dir'",
"added './fixtures/file.txt' as a file dependency",
"begin globbing './fixtures/file.txt'...",
"determined './fixtures/file.txt' is a file",
Expand All @@ -229,7 +234,7 @@ Object {
"read './fixtures/file.txt'",
"reading './fixtures/file.txt'...",
"starting to add additional assets...",
"starting to process a pattern from 'file.txt' using './fixtures' context to '.'...",
"starting to process a pattern from 'file.txt' using './fixtures' context",
"writing 'file.txt' from './fixtures/file.txt' to compilation assets...",
"written 'file.txt' from './fixtures/file.txt' to compilation assets",
],
Expand All @@ -239,6 +244,9 @@ Object {
exports[`CopyPlugin logging should logging when "from" is a glob: logs 1`] = `
Object {
"logs": Array [
"'to' option '.' determinated as 'dir'",
"'to' option '.' determinated as 'dir'",
"'to' option '.' determinated as 'dir'",
"added './fixtures/directory' as a context dependency",
"added './fixtures/directory/directoryfile.txt' as a file dependency",
"added './fixtures/directory/nested/deep-nested/deepnested.txt' as a file dependency",
Expand All @@ -260,7 +268,7 @@ Object {
"reading './fixtures/directory/nested/deep-nested/deepnested.txt'...",
"reading './fixtures/directory/nested/nestedfile.txt'...",
"starting to add additional assets...",
"starting to process a pattern from 'directory/**' using './fixtures' context to '.'...",
"starting to process a pattern from 'directory/**' using './fixtures' context",
"writing 'directory/directoryfile.txt' from './fixtures/directory/directoryfile.txt' to compilation assets...",
"writing 'directory/nested/deep-nested/deepnested.txt' from './fixtures/directory/nested/deep-nested/deepnested.txt' to compilation assets...",
"writing 'directory/nested/nestedfile.txt' from './fixtures/directory/nested/nestedfile.txt' to compilation assets...",
Expand All @@ -271,6 +279,28 @@ Object {
}
`;

exports[`CopyPlugin logging should logging when 'to' is a function: logs 1`] = `
Object {
"logs": Array [
"'to' option 'newFile.txt' determinated as 'file'",
"added './fixtures/file.txt' as a file dependency",
"begin globbing './fixtures/file.txt'...",
"determined './fixtures/file.txt' is a file",
"determined that './fixtures/file.txt' should write to 'newFile.txt'",
"finished to adding additional assets",
"finished to process a pattern from 'file.txt' using './fixtures' context to 'newFile.txt'",
"found './fixtures/file.txt'",
"getting stats for './fixtures/file.txt'...",
"read './fixtures/file.txt'",
"reading './fixtures/file.txt'...",
"starting to add additional assets...",
"starting to process a pattern from 'file.txt' using './fixtures' context",
"writing 'newFile.txt' from './fixtures/file.txt' to compilation assets...",
"written 'newFile.txt' from './fixtures/file.txt' to compilation assets",
],
}
`;

exports[`CopyPlugin stats should work have assets info: assets 1`] = `
Object {
".dottedfile": "dottedfile contents
Expand Down
9 changes: 8 additions & 1 deletion test/__snapshots__/validate-options.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,14 @@ 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].to should be a string."
- options.patterns[0] should be one of these:
non-empty string | object { from, to?, context?, globOptions?, filter?, toType?, force?, flatten?, transform?, cacheTransform?, transformPath?, noErrorOnMissing? }
Details:
* options.patterns[0].to should be one of these:
string | function
Details:
* options.patterns[0].to should be a string.
* options.patterns[0].to should be an instance of function."
`;
exports[`validate options should throw an error on the "patterns" option with "[{"from":{"glob":"**/*","dot":false},"to":"dir","context":"context"}]" value 1`] = `
Expand Down
Loading

0 comments on commit 9bc5416

Please sign in to comment.