Repository: webpack-contrib/copy-webpack-plugin
Pull request #530 (2020-09-21 14:22:58)

Refactor code
<!--
  HOLY CRAP a Pull Request. We ❤️ those!

  If you remove or skip this template, you'll make the 🐼 sad and the mighty god
  of Github will appear and pile-drive the close button from a great height
  while making animal noises.

  Please place an x (no spaces!) in all [ ] that apply
-->

This PR contains a:

- [ ] **bugfix**
- [ ] new **feature**
- [x] **code refactor**
- [x] **test update** <!-- if bug or feature is checked, this should be too -->
- [ ] **typo fix**
- [ ] **metadata update**

### Motivation / Use-Case

refactor code for cache implementation between builds

### Breaking Changes

No

### Additional Info

fix logging in some cases

codecov[bot]: # [Codecov](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530?src=pr&el=h1) Report
> Merging [#530](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530?src=pr&el=desc) into [master](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/commit/58a574b16637b05aaabcd2291d95dc093933a4ae?el=desc) will **decrease** coverage by `0.12%`.
> The diff coverage is `97.35%`.

[![Impacted file tree graph](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530/graphs/tree.svg?width=650&height=150&src=pr&token=fEoU9iBFGL)](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #530      +/-   ##
==========================================
- Coverage   97.75%   97.63%   -0.13%     
==========================================
  Files           8        3       -5     
  Lines         223      211      -12     
  Branches       63       66       +3     
==========================================
- Hits          218      206      -12     
  Misses          5        5              
```


| [Impacted Files](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/index.js](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530/diff?src=pr&el=tree#diff-c3JjL2luZGV4Lmpz) | `97.96% <97.35%> (-2.04%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530?src=pr&el=footer). Last update [58a574b...59f075d](https://codecov.io/gh/webpack-contrib/copy-webpack-plugin/pull/530?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/index.js b/src/index.js
index f2b8b65..b8fbbce 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,17 +1,31 @@
+import path from 'path';
+import os from 'os';
+import crypto from 'crypto';
+
 import webpack from 'webpack';
 import validateOptions from 'schema-utils';
 import pLimit from 'p-limit';
+import globby from 'globby';
+import findCacheDir from 'find-cache-dir';
+import serialize from 'serialize-javascript';
+import cacache from 'cacache';
+import loaderUtils from 'loader-utils';
+import normalizePath from 'normalize-path';
+import globParent from 'glob-parent';
+import fastGlob from 'fast-glob';
+
+import { version } from '../package.json';
 
 import schema from './options.json';
-import preProcessPattern from './preProcessPattern';
-import processPattern from './processPattern';
-import postProcessPattern from './postProcessPattern';
+import { readFile, stat } from './utils/promisify';
 
 // webpack 5 exposes the sources property to ensure the right version of webpack-sources is used
 const { RawSource } =
   // eslint-disable-next-line global-require
   webpack.sources || require('webpack-sources');
 
+const template = /(\[ext\])|(\[name\])|(\[path\])|(\[folder\])|(\[emoji(?::(\d+))?\])|(\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\])|(\[\d+\])/;
+
 class CopyPlugin {
   constructor(options = {}) {
     validateOptions(schema, options, {
@@ -23,6 +37,341 @@ class CopyPlugin {
     this.options = options.options || {};
   }
 
+  // eslint-disable-next-line class-methods-use-this
+  async runPattern(compiler, compilation, logger, inputPattern) {
+    const pattern =
+      typeof inputPattern === 'string'
+        ? { from: inputPattern }
+        : { ...inputPattern };
+
+    pattern.fromOrigin = pattern.from;
+    pattern.from = path.normalize(pattern.from);
+    pattern.to = path.normalize(
+      typeof pattern.to !== 'undefined' ? pattern.to : ''
+    );
+
+    pattern.context = path.normalize(
+      typeof pattern.context !== 'undefined'
+        ? !path.isAbsolute(pattern.context)
+          ? path.join(compiler.options.context, pattern.context)
+          : pattern.context
+        : compiler.options.context
+    );
+
+    logger.debug(`processing from "${pattern.from}" to "${pattern.to}"`);
+
+    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 {
+      pattern.absoluteFrom = path.resolve(pattern.context, pattern.from);
+    }
+
+    logger.debug(
+      `getting stats for "${pattern.absoluteFrom}" to determinate "fromType"`
+    );
+
+    let stats;
+
+    try {
+      stats = await stat(compiler.inputFileSystem, pattern.absoluteFrom);
+    } catch (error) {
+      // Nothing
+    }
+
+    if (stats) {
+      if (stats.isDirectory()) {
+        pattern.fromType = 'dir';
+      } else if (stats.isFile()) {
+        pattern.fromType = 'file';
+      }
+    }
+
+    // eslint-disable-next-line no-param-reassign
+    pattern.globOptions = {
+      ...{ followSymbolicLinks: true },
+      ...(pattern.globOptions || {}),
+      ...{ cwd: pattern.context, objectMode: true },
+    };
+
+    switch (pattern.fromType) {
+      case 'dir':
+        logger.debug(`determined "${pattern.absoluteFrom}" is a directory`);
+
+        compilation.contextDependencies.add(pattern.absoluteFrom);
+
+        logger.debug(`add "${pattern.absoluteFrom}" as a context dependency`);
+
+        /* eslint-disable no-param-reassign */
+        pattern.context = pattern.absoluteFrom;
+        pattern.glob = path.posix.join(
+          fastGlob.escapePath(
+            normalizePath(path.resolve(pattern.absoluteFrom))
+          ),
+          '**/*'
+        );
+        pattern.absoluteFrom = path.join(pattern.absoluteFrom, '**/*');
+
+        if (typeof pattern.globOptions.dot === 'undefined') {
+          pattern.globOptions.dot = true;
+        }
+        /* eslint-enable no-param-reassign */
+        break;
+      case 'file':
+        logger.debug(`determined "${pattern.absoluteFrom}" is a file`);
+
+        compilation.fileDependencies.add(pattern.absoluteFrom);
+
+        logger.debug(`add "${pattern.absoluteFrom}" as a file dependency`);
+
+        /* eslint-disable no-param-reassign */
+        pattern.context = path.dirname(pattern.absoluteFrom);
+        pattern.glob = fastGlob.escapePath(
+          normalizePath(path.resolve(pattern.absoluteFrom))
+        );
+
+        if (typeof pattern.globOptions.dot === 'undefined') {
+          pattern.globOptions.dot = true;
+        }
+        /* eslint-enable no-param-reassign */
+        break;
+      default: {
+        logger.debug(`determined "${pattern.absoluteFrom}" is a glob`);
+
+        const contextDependencies = path.normalize(
+          globParent(pattern.absoluteFrom)
+        );
+
+        compilation.contextDependencies.add(contextDependencies);
+
+        logger.debug(`add "${contextDependencies}" as a context dependency`);
+
+        /* eslint-disable no-param-reassign */
+        pattern.fromType = 'glob';
+        pattern.glob = path.isAbsolute(pattern.fromOrigin)
+          ? pattern.fromOrigin
+          : path.posix.join(
+              fastGlob.escapePath(normalizePath(path.resolve(pattern.context))),
+              pattern.fromOrigin
+            );
+        /* eslint-enable no-param-reassign */
+      }
+    }
+
+    logger.log(
+      `begin globbing "${pattern.glob}" with a context of "${pattern.context}"`
+    );
+
+    const paths = await globby(pattern.glob, pattern.globOptions);
+
+    if (paths.length === 0) {
+      if (pattern.noErrorOnMissing) {
+        return Promise.resolve();
+      }
+
+      const missingError = new Error(
+        `unable to locate "${pattern.from}" at "${pattern.absoluteFrom}"`
+      );
+
+      logger.error(missingError.message);
+
+      compilation.errors.push(missingError);
+
+      return Promise.resolve();
+    }
+
+    const filteredPaths = (
+      await Promise.all(
+        paths.map(async (item) => {
+          // Exclude directories
+          if (!item.dirent.isFile()) {
+            return false;
+          }
+
+          if (pattern.filter) {
+            const isFiltered = await pattern.filter(item.path);
+
+            return isFiltered ? item : false;
+          }
+
+          return item;
+        })
+      )
+    ).filter((item) => item);
+
+    if (filteredPaths.length === 0) {
+      return Promise.resolve();
+    }
+
+    const files = filteredPaths.map((item) => {
+      const from = item.path;
+
+      logger.debug(`found "${from}"`);
+
+      // `globby`/`fast-glob` return the relative path when the path contains special characters on windows
+      const absoluteFrom = path.resolve(pattern.context, from);
+      const relativeFrom = pattern.flatten
+        ? path.basename(absoluteFrom)
+        : path.relative(pattern.context, absoluteFrom);
+      let webpackTo =
+        pattern.toType === 'dir'
+          ? path.join(pattern.to, relativeFrom)
+          : pattern.to;
+
+      if (path.isAbsolute(webpackTo)) {
+        webpackTo = path.relative(compiler.options.output.path, webpackTo);
+      }
+
+      logger.log(`determined that "${from}" should write to "${webpackTo}"`);
+
+      return { absoluteFrom, relativeFrom, webpackTo };
+    });
+
+    return Promise.all(
+      files.map(async (file) => {
+        // If this came from a glob, add it to the file watchlist
+        if (pattern.fromType === 'glob') {
+          logger.debug(`add ${file.absoluteFrom} as fileDependencies`);
+
+          compilation.fileDependencies.add(file.absoluteFrom);
+        }
+
+        logger.debug(`reading "${file.absoluteFrom}" to write to assets`);
+
+        let data;
+
+        try {
+          data = await readFile(compiler.inputFileSystem, file.absoluteFrom);
+        } catch (error) {
+          compilation.errors.push(error);
+
+          return;
+        }
+
+        if (pattern.transform) {
+          logger.log(`transforming content for "${file.absoluteFrom}"`);
+
+          if (pattern.cacheTransform) {
+            const cacheDirectory = pattern.cacheTransform.directory
+              ? pattern.cacheTransform.directory
+              : typeof pattern.cacheTransform === 'string'
+              ? pattern.cacheTransform
+              : findCacheDir({ name: 'copy-webpack-plugin' }) || os.tmpdir();
+            let defaultCacheKeys = {
+              version,
+              transform: pattern.transform,
+              contentHash: crypto.createHash('md4').update(data).digest('hex'),
+            };
+
+            if (typeof pattern.cacheTransform.keys === 'function') {
+              defaultCacheKeys = await pattern.cacheTransform.keys(
+                defaultCacheKeys,
+                file.absoluteFrom
+              );
+            } else {
+              defaultCacheKeys = {
+                ...defaultCacheKeys,
+                ...pattern.cacheTransform.keys,
+              };
+            }
+
+            const cacheKeys = serialize(defaultCacheKeys);
+
+            try {
+              const result = await cacache.get(cacheDirectory, cacheKeys);
+
+              logger.debug(
+                `getting cached transformation for "${file.absoluteFrom}"`
+              );
+
+              ({ data } = result);
+            } catch (_ignoreError) {
+              data = await pattern.transform(data, file.absoluteFrom);
+
+              logger.debug(`caching transformation for "${file.absoluteFrom}"`);
+
+              await cacache.put(cacheDirectory, cacheKeys, data);
+            }
+          } else {
+            data = await pattern.transform(data, file.absoluteFrom);
+          }
+        }
+
+        if (pattern.toType === 'template') {
+          logger.log(
+            `interpolating template "${file.webpackTo}" for "${file.relativeFrom}"`
+          );
+
+          // If it doesn't have an extension, remove it from the pattern
+          // ie. [name].[ext] or [name][ext] both become [name]
+          if (!path.extname(file.relativeFrom)) {
+            // eslint-disable-next-line no-param-reassign
+            file.webpackTo = file.webpackTo.replace(/\.?\[ext]/g, '');
+          }
+
+          // eslint-disable-next-line no-param-reassign
+          file.immutable = /\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi.test(
+            file.webpackTo
+          );
+
+          // eslint-disable-next-line no-param-reassign
+          file.webpackTo = loaderUtils.interpolateName(
+            { resourcePath: file.absoluteFrom },
+            file.webpackTo,
+            {
+              content: data,
+              context: pattern.context,
+            }
+          );
+
+          // Bug in `loader-utils`, package convert `\\` to `/`, need fix in loader-utils
+          // eslint-disable-next-line no-param-reassign
+          file.webpackTo = path.normalize(file.webpackTo);
+        }
+
+        if (pattern.transformPath) {
+          logger.log(
+            `transforming path "${file.webpackTo}" for "${file.absoluteFrom}"`
+          );
+
+          // eslint-disable-next-line no-param-reassign
+          file.immutable = false;
+          // eslint-disable-next-line no-param-reassign
+          file.webpackTo = await pattern.transformPath(
+            file.webpackTo,
+            file.absoluteFrom
+          );
+        }
+
+        // eslint-disable-next-line no-param-reassign
+        file.data = data;
+        // eslint-disable-next-line no-param-reassign
+        file.targetPath = normalizePath(file.webpackTo);
+        // eslint-disable-next-line no-param-reassign
+        file.force = pattern.force;
+
+        // eslint-disable-next-line consistent-return
+        return file;
+      })
+    );
+  }
+
   apply(compiler) {
     const pluginName = this.constructor.name;
     const limit = pLimit(this.options.concurrency || 100);
@@ -35,46 +384,14 @@ class CopyPlugin {
         async (callback) => {
           logger.debug('start to adding additional assets');
 
-          const globalRef = {
-            context: compiler.options.context,
-            logger,
-            compilation,
-            inputFileSystem: compiler.inputFileSystem,
-            output: compiler.options.output.path,
-          };
-
           let assets;
 
           try {
             assets = await Promise.all(
-              this.patterns.map((pattern) =>
-                limit(async () => {
-                  const patternAfterPreProcess = await preProcessPattern(
-                    globalRef,
-                    pattern
-                  );
-
-                  const files = await processPattern(
-                    globalRef,
-                    patternAfterPreProcess
-                  );
-
-                  if (!files) {
-                    return Promise.resolve();
-                  }
-
-                  return Promise.all(
-                    files
-                      .filter(Boolean)
-                      .map((file) =>
-                        postProcessPattern(
-                          globalRef,
-                          patternAfterPreProcess,
-                          file
-                        )
-                      )
-                  );
-                })
+              this.patterns.map((item) =>
+                limit(async () =>
+                  this.runPattern(compiler, compilation, logger, item)
+                )
               )
             );
           } catch (error) {
@@ -115,7 +432,7 @@ class CopyPlugin {
               if (existingAsset) {
                 if (force) {
                   logger.log(
-                    `force updating '${webpackTo}' to compilation assets from '${absoluteFrom}'`
+                    `force updating "${webpackTo}" to compilation assets from "${absoluteFrom}"`
                   );
 
                   const info = { copied: true };
@@ -130,14 +447,14 @@ class CopyPlugin {
                 }
 
                 logger.log(
-                  `skipping '${webpackTo}', because it already exists`
+                  `skipping "${webpackTo}", because it already exists`
                 );
 
                 return;
               }
 
               logger.log(
-                `writing '${webpackTo}' to compilation assets from '${absoluteFrom}'`
+                `writing "${webpackTo}" to compilation assets from "${absoluteFrom}"`
               );
 
               const info = { copied: true };
diff --git a/src/postProcessPattern.js b/src/postProcessPattern.js
deleted file mode 100644
index d7d96e0..0000000
--- a/src/postProcessPattern.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import path from 'path';
-import os from 'os';
-import crypto from 'crypto';
-
-import loaderUtils from 'loader-utils';
-import cacache from 'cacache';
-import serialize from 'serialize-javascript';
-import findCacheDir from 'find-cache-dir';
-import normalizePath from 'normalize-path';
-
-import { version } from '../package.json';
-
-import { readFile } from './utils/promisify';
-
-/* eslint-disable no-param-reassign */
-export default async function postProcessPattern(globalRef, pattern, file) {
-  const { logger, compilation, inputFileSystem } = globalRef;
-
-  // If this came from a glob, add it to the file watchlist
-  if (pattern.fromType === 'glob') {
-    logger.debug(`add ${file.absoluteFrom} as fileDependencies`);
-
-    compilation.fileDependencies.add(file.absoluteFrom);
-  }
-
-  logger.debug(`reading '${file.absoluteFrom}' to write to assets`);
-
-  let data;
-
-  try {
-    data = await readFile(inputFileSystem, file.absoluteFrom);
-  } catch (error) {
-    compilation.errors.push(error);
-
-    return;
-  }
-
-  if (pattern.transform) {
-    logger.log(`transforming content for '${file.absoluteFrom}'`);
-
-    if (pattern.cacheTransform) {
-      const cacheDirectory = pattern.cacheTransform.directory
-        ? pattern.cacheTransform.directory
-        : typeof pattern.cacheTransform === 'string'
-        ? pattern.cacheTransform
-        : findCacheDir({ name: 'copy-webpack-plugin' }) || os.tmpdir();
-      let defaultCacheKeys = {
-        version,
-        transform: pattern.transform,
-        contentHash: crypto.createHash('md4').update(data).digest('hex'),
-      };
-
-      if (typeof pattern.cacheTransform.keys === 'function') {
-        defaultCacheKeys = await pattern.cacheTransform.keys(
-          defaultCacheKeys,
-          file.absoluteFrom
-        );
-      } else {
-        defaultCacheKeys = {
-          ...defaultCacheKeys,
-          ...pattern.cacheTransform.keys,
-        };
-      }
-
-      const cacheKeys = serialize(defaultCacheKeys);
-
-      try {
-        const result = await cacache.get(cacheDirectory, cacheKeys);
-
-        logger.debug(
-          `getting cached transformation for '${file.absoluteFrom}'`
-        );
-
-        ({ data } = result);
-      } catch (_ignoreError) {
-        data = await pattern.transform(data, file.absoluteFrom);
-
-        logger.debug(`caching transformation for '${file.absoluteFrom}'`);
-
-        await cacache.put(cacheDirectory, cacheKeys, data);
-      }
-    } else {
-      data = await pattern.transform(data, file.absoluteFrom);
-    }
-  }
-
-  if (pattern.toType === 'template') {
-    logger.log(
-      `interpolating template '${file.webpackTo}' for '${file.relativeFrom}'`
-    );
-
-    // If it doesn't have an extension, remove it from the pattern
-    // ie. [name].[ext] or [name][ext] both become [name]
-    if (!path.extname(file.relativeFrom)) {
-      file.webpackTo = file.webpackTo.replace(/\.?\[ext]/g, '');
-    }
-
-    file.immutable = /\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi.test(
-      file.webpackTo
-    );
-
-    file.webpackTo = loaderUtils.interpolateName(
-      { resourcePath: file.absoluteFrom },
-      file.webpackTo,
-      {
-        content: data,
-        context: pattern.context,
-      }
-    );
-
-    // Bug in `loader-utils`, package convert `\\` to `/`, need fix in loader-utils
-    file.webpackTo = path.normalize(file.webpackTo);
-  }
-
-  if (pattern.transformPath) {
-    logger.log(
-      `transforming path '${file.webpackTo}' for '${file.absoluteFrom}'`
-    );
-
-    file.immutable = false;
-    file.webpackTo = await pattern.transformPath(
-      file.webpackTo,
-      file.absoluteFrom
-    );
-  }
-
-  file.data = data;
-  file.targetPath = normalizePath(file.webpackTo);
-  file.force = pattern.force;
-
-  // eslint-disable-next-line consistent-return
-  return file;
-}
diff --git a/src/preProcessPattern.js b/src/preProcessPattern.js
deleted file mode 100644
index 99f952d..0000000
--- a/src/preProcessPattern.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import path from 'path';
-
-import isTemplateLike from './utils/isTemplateLike';
-import { stat } from './utils/promisify';
-
-/* eslint-disable no-param-reassign */
-
-export default async function preProcessPattern(globalRef, pattern) {
-  const { context, logger, inputFileSystem } = globalRef;
-
-  pattern = typeof pattern === 'string' ? { from: pattern } : { ...pattern };
-  pattern.fromOrigin = pattern.from;
-  pattern.from = path.normalize(pattern.from);
-  pattern.to = path.normalize(
-    typeof pattern.to !== 'undefined' ? pattern.to : ''
-  );
-  pattern.context = path.normalize(
-    typeof pattern.context !== 'undefined'
-      ? !path.isAbsolute(pattern.context)
-        ? path.join(context, pattern.context)
-        : pattern.context
-      : context
-  );
-
-  logger.debug(`processing from: '${pattern.from}' to: '${pattern.to}'`);
-
-  const isToDirectory =
-    path.extname(pattern.to) === '' || pattern.to.slice(-1) === path.sep;
-
-  switch (true) {
-    // if toType already exists
-    case !!pattern.toType:
-      break;
-    case isTemplateLike(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 {
-    pattern.absoluteFrom = path.resolve(pattern.context, pattern.from);
-  }
-
-  logger.debug(
-    `getting stats for '${pattern.absoluteFrom}' to determinate 'fromType'`
-  );
-
-  let stats;
-
-  try {
-    stats = await stat(inputFileSystem, pattern.absoluteFrom);
-  } catch (error) {
-    return pattern;
-  }
-
-  if (stats.isDirectory()) {
-    pattern.fromType = 'dir';
-  } else if (stats.isFile()) {
-    pattern.fromType = 'file';
-  }
-
-  return pattern;
-}
diff --git a/src/processPattern.js b/src/processPattern.js
deleted file mode 100644
index a961a50..0000000
--- a/src/processPattern.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import path from 'path';
-
-import globby from 'globby';
-
-import createPatternGlob from './utils/createPatternGlob';
-
-export default async function processPattern(globalRef, pattern) {
-  const { logger, output, compilation } = globalRef;
-
-  createPatternGlob(pattern, globalRef);
-
-  logger.log(
-    `begin globbing '${pattern.glob}' with a context of '${pattern.context}'`
-  );
-
-  const paths = await globby(pattern.glob, pattern.globOptions);
-
-  if (paths.length === 0) {
-    if (pattern.noErrorOnMissing) {
-      return Promise.resolve();
-    }
-
-    const missingError = new Error(
-      `unable to locate '${pattern.from}' at '${pattern.absoluteFrom}'`
-    );
-
-    logger.error(missingError.message);
-
-    compilation.errors.push(missingError);
-
-    return Promise.resolve();
-  }
-
-  const filteredPaths = (
-    await Promise.all(
-      paths.map(async (item) => {
-        // Exclude directories
-        if (!item.dirent.isFile()) {
-          return false;
-        }
-
-        if (pattern.filter) {
-          const isFiltered = await pattern.filter(item.path);
-
-          return isFiltered ? item : false;
-        }
-
-        return item;
-      })
-    )
-  ).filter((item) => item);
-
-  return filteredPaths.map((item) => {
-    const from = item.path;
-
-    logger.debug(`found ${from}`);
-
-    // `globby`/`fast-glob` return the relative path when the path contains special characters on windows
-    const absoluteFrom = path.resolve(pattern.context, from);
-    const relativeFrom = pattern.flatten
-      ? path.basename(absoluteFrom)
-      : path.relative(pattern.context, absoluteFrom);
-    let webpackTo =
-      pattern.toType === 'dir'
-        ? path.join(pattern.to, relativeFrom)
-        : pattern.to;
-
-    if (path.isAbsolute(webpackTo)) {
-      webpackTo = path.relative(output, webpackTo);
-    }
-
-    logger.log(`determined that '${from}' should write to '${webpackTo}'`);
-
-    return { absoluteFrom, relativeFrom, webpackTo };
-  });
-}
diff --git a/src/utils/createPatternGlob.js b/src/utils/createPatternGlob.js
deleted file mode 100644
index 4e9033a..0000000
--- a/src/utils/createPatternGlob.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import path from 'path';
-
-import normalizePath from 'normalize-path';
-import globParent from 'glob-parent';
-import fastGlob from 'fast-glob';
-
-function getAbsoluteContext(context) {
-  return fastGlob.escapePath(normalizePath(path.resolve(context)));
-}
-
-function createPatternGlob(pattern, globalRef) {
-  const { logger, compilation } = globalRef;
-
-  // eslint-disable-next-line no-param-reassign
-  pattern.globOptions = {
-    ...{ followSymbolicLinks: true },
-    ...(pattern.globOptions || {}),
-    ...{ cwd: pattern.context, objectMode: true },
-  };
-
-  switch (pattern.fromType) {
-    case 'dir':
-      logger.debug(`determined '${pattern.absoluteFrom}' is a directory`);
-      logger.debug(`add ${pattern.absoluteFrom} as contextDependencies`);
-
-      compilation.contextDependencies.add(pattern.absoluteFrom);
-
-      /* eslint-disable no-param-reassign */
-      pattern.context = pattern.absoluteFrom;
-      pattern.glob = path.posix.join(
-        getAbsoluteContext(pattern.absoluteFrom),
-        '**/*'
-      );
-      pattern.absoluteFrom = path.join(pattern.absoluteFrom, '**/*');
-
-      if (typeof pattern.globOptions.dot === 'undefined') {
-        pattern.globOptions.dot = true;
-      }
-      /* eslint-enable no-param-reassign */
-      break;
-    case 'file':
-      logger.debug(`determined '${pattern.absoluteFrom}' is a file`);
-      logger.debug(`add ${pattern.absoluteFrom} as fileDependencies`);
-
-      compilation.fileDependencies.add(pattern.absoluteFrom);
-
-      /* eslint-disable no-param-reassign */
-      pattern.context = path.dirname(pattern.absoluteFrom);
-      pattern.glob = getAbsoluteContext(pattern.absoluteFrom);
-
-      if (typeof pattern.globOptions.dot === 'undefined') {
-        pattern.globOptions.dot = true;
-      }
-      /* eslint-enable no-param-reassign */
-      break;
-    default: {
-      logger.debug(`determined '${pattern.absoluteFrom}' is a glob`);
-
-      const contextDependencies = path.normalize(
-        globParent(pattern.absoluteFrom)
-      );
-
-      logger.debug(`add ${contextDependencies} as contextDependencies`);
-
-      compilation.contextDependencies.add(contextDependencies);
-
-      /* eslint-disable no-param-reassign */
-      pattern.fromType = 'glob';
-      pattern.glob = path.isAbsolute(pattern.fromOrigin)
-        ? pattern.fromOrigin
-        : path.posix.join(
-            getAbsoluteContext(pattern.context),
-            pattern.fromOrigin
-          );
-      /* eslint-enable no-param-reassign */
-    }
-  }
-
-  return pattern;
-}
-
-export default createPatternGlob;
diff --git a/src/utils/isTemplateLike.js b/src/utils/isTemplateLike.js
deleted file mode 100644
index a06b396..0000000
--- a/src/utils/isTemplateLike.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export default (pattern) =>
-  /(\[ext\])|(\[name\])|(\[path\])|(\[folder\])|(\[emoji(?::(\d+))?\])|(\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\])|(\[\d+\])/.test(
-    pattern
-  );


Test patch:
diff --git a/test/__snapshots__/CopyPlugin.test.js.snap b/test/__snapshots__/CopyPlugin.test.js.snap
index 9294be1..4c768dc 100644
--- a/test/__snapshots__/CopyPlugin.test.js.snap
+++ b/test/__snapshots__/CopyPlugin.test.js.snap
@@ -3,29 +3,29 @@
 exports[`CopyPlugin logging should logging when "from" is a directory: logs 1`] = `
 Object {
   "logs": Array [
-    "add ./fixtures/directory as contextDependencies",
-    "begin globbing './fixtures/directory/**/*' with a context of './fixtures/directory'",
-    "determined './fixtures/directory' is a directory",
-    "determined that './fixtures/directory/.dottedfile' should write to '.dottedfile'",
-    "determined that './fixtures/directory/directoryfile.txt' should write to 'directoryfile.txt'",
-    "determined that './fixtures/directory/nested/deep-nested/deepnested.txt' should write to 'nested/deep-nested/deepnested.txt'",
-    "determined that './fixtures/directory/nested/nestedfile.txt' should write to 'nested/nestedfile.txt'",
+    "add \\"./fixtures/directory\\" as a context dependency",
+    "begin globbing \\"./fixtures/directory/**/*\\" with a context of \\"./fixtures/directory\\"",
+    "determined \\"./fixtures/directory\\" is a directory",
+    "determined that \\"./fixtures/directory/.dottedfile\\" should write to \\".dottedfile\\"",
+    "determined that \\"./fixtures/directory/directoryfile.txt\\" should write to \\"directoryfile.txt\\"",
+    "determined that \\"./fixtures/directory/nested/deep-nested/deepnested.txt\\" should write to \\"nested/deep-nested/deepnested.txt\\"",
+    "determined that \\"./fixtures/directory/nested/nestedfile.txt\\" should write to \\"nested/nestedfile.txt\\"",
     "end to adding additional assets",
-    "found ./fixtures/directory/.dottedfile",
-    "found ./fixtures/directory/directoryfile.txt",
-    "found ./fixtures/directory/nested/deep-nested/deepnested.txt",
-    "found ./fixtures/directory/nested/nestedfile.txt",
-    "getting stats for './fixtures/directory' to determinate 'fromType'",
-    "processing from: 'directory' to: '.'",
-    "reading './fixtures/directory/.dottedfile' to write to assets",
-    "reading './fixtures/directory/directoryfile.txt' to write to assets",
-    "reading './fixtures/directory/nested/deep-nested/deepnested.txt' to write to assets",
-    "reading './fixtures/directory/nested/nestedfile.txt' to write to assets",
+    "found \\"./fixtures/directory/.dottedfile\\"",
+    "found \\"./fixtures/directory/directoryfile.txt\\"",
+    "found \\"./fixtures/directory/nested/deep-nested/deepnested.txt\\"",
+    "found \\"./fixtures/directory/nested/nestedfile.txt\\"",
+    "getting stats for \\"./fixtures/directory\\" to determinate \\"fromType\\"",
+    "processing from \\"directory\\" to \\".\\"",
+    "reading \\"./fixtures/directory/.dottedfile\\" to write to assets",
+    "reading \\"./fixtures/directory/directoryfile.txt\\" to write to assets",
+    "reading \\"./fixtures/directory/nested/deep-nested/deepnested.txt\\" to write to assets",
+    "reading \\"./fixtures/directory/nested/nestedfile.txt\\" to write to assets",
     "start to adding additional assets",
-    "writing '.dottedfile' to compilation assets from './fixtures/directory/.dottedfile'",
-    "writing 'directoryfile.txt' to compilation assets from './fixtures/directory/directoryfile.txt'",
-    "writing 'nested/deep-nested/deepnested.txt' to compilation assets from './fixtures/directory/nested/deep-nested/deepnested.txt'",
-    "writing 'nested/nestedfile.txt' to compilation assets from './fixtures/directory/nested/nestedfile.txt'",
+    "writing \\".dottedfile\\" to compilation assets from \\"./fixtures/directory/.dottedfile\\"",
+    "writing \\"directoryfile.txt\\" to compilation assets from \\"./fixtures/directory/directoryfile.txt\\"",
+    "writing \\"nested/deep-nested/deepnested.txt\\" to compilation assets from \\"./fixtures/directory/nested/deep-nested/deepnested.txt\\"",
+    "writing \\"nested/nestedfile.txt\\" to compilation assets from \\"./fixtures/directory/nested/nestedfile.txt\\"",
   ],
 }
 `;
@@ -33,17 +33,17 @@ Object {
 exports[`CopyPlugin logging should logging when "from" is a file: logs 1`] = `
 Object {
   "logs": Array [
-    "add ./fixtures/file.txt as fileDependencies",
-    "begin globbing './fixtures/file.txt' with a context of './fixtures'",
-    "determined './fixtures/file.txt' is a file",
-    "determined that './fixtures/file.txt' should write to 'file.txt'",
+    "add \\"./fixtures/file.txt\\" as a file dependency",
+    "begin globbing \\"./fixtures/file.txt\\" with a context of \\"./fixtures\\"",
+    "determined \\"./fixtures/file.txt\\" is a file",
+    "determined that \\"./fixtures/file.txt\\" should write to \\"file.txt\\"",
     "end to adding additional assets",
-    "found ./fixtures/file.txt",
-    "getting stats for './fixtures/file.txt' to determinate 'fromType'",
-    "processing from: 'file.txt' to: '.'",
-    "reading './fixtures/file.txt' to write to assets",
+    "found \\"./fixtures/file.txt\\"",
+    "getting stats for \\"./fixtures/file.txt\\" to determinate \\"fromType\\"",
+    "processing from \\"file.txt\\" to \\".\\"",
+    "reading \\"./fixtures/file.txt\\" to write to assets",
     "start to adding additional assets",
-    "writing 'file.txt' to compilation assets from './fixtures/file.txt'",
+    "writing \\"file.txt\\" to compilation assets from \\"./fixtures/file.txt\\"",
   ],
 }
 `;
@@ -51,28 +51,28 @@ Object {
 exports[`CopyPlugin logging should logging when "from" is a glob: logs 1`] = `
 Object {
   "logs": Array [
-    "add ./fixtures/directory as contextDependencies",
+    "add \\"./fixtures/directory\\" as a context dependency",
     "add ./fixtures/directory/directoryfile.txt as fileDependencies",
     "add ./fixtures/directory/nested/deep-nested/deepnested.txt as fileDependencies",
     "add ./fixtures/directory/nested/nestedfile.txt as fileDependencies",
-    "begin globbing './fixtures/directory/**' with a context of './fixtures'",
-    "determined './fixtures/directory/**' is a glob",
-    "determined that './fixtures/directory/directoryfile.txt' should write to 'directory/directoryfile.txt'",
-    "determined that './fixtures/directory/nested/deep-nested/deepnested.txt' should write to 'directory/nested/deep-nested/deepnested.txt'",
-    "determined that './fixtures/directory/nested/nestedfile.txt' should write to 'directory/nested/nestedfile.txt'",
+    "begin globbing \\"./fixtures/directory/**\\" with a context of \\"./fixtures\\"",
+    "determined \\"./fixtures/directory/**\\" is a glob",
+    "determined that \\"./fixtures/directory/directoryfile.txt\\" should write to \\"directory/directoryfile.txt\\"",
+    "determined that \\"./fixtures/directory/nested/deep-nested/deepnested.txt\\" should write to \\"directory/nested/deep-nested/deepnested.txt\\"",
+    "determined that \\"./fixtures/directory/nested/nestedfile.txt\\" should write to \\"directory/nested/nestedfile.txt\\"",
     "end to adding additional assets",
-    "found ./fixtures/directory/directoryfile.txt",
-    "found ./fixtures/directory/nested/deep-nested/deepnested.txt",
-    "found ./fixtures/directory/nested/nestedfile.txt",
-    "getting stats for './fixtures/directory/**' to determinate 'fromType'",
-    "processing from: 'directory/**' to: '.'",
-    "reading './fixtures/directory/directoryfile.txt' to write to assets",
-    "reading './fixtures/directory/nested/deep-nested/deepnested.txt' to write to assets",
-    "reading './fixtures/directory/nested/nestedfile.txt' to write to assets",
+    "found \\"./fixtures/directory/directoryfile.txt\\"",
+    "found \\"./fixtures/directory/nested/deep-nested/deepnested.txt\\"",
+    "found \\"./fixtures/directory/nested/nestedfile.txt\\"",
+    "getting stats for \\"./fixtures/directory/**\\" to determinate \\"fromType\\"",
+    "processing from \\"directory/**\\" to \\".\\"",
+    "reading \\"./fixtures/directory/directoryfile.txt\\" to write to assets",
+    "reading \\"./fixtures/directory/nested/deep-nested/deepnested.txt\\" to write to assets",
+    "reading \\"./fixtures/directory/nested/nestedfile.txt\\" to write to assets",
     "start to adding additional assets",
-    "writing 'directory/directoryfile.txt' to compilation assets from './fixtures/directory/directoryfile.txt'",
-    "writing 'directory/nested/deep-nested/deepnested.txt' to compilation assets from './fixtures/directory/nested/deep-nested/deepnested.txt'",
-    "writing 'directory/nested/nestedfile.txt' to compilation assets from './fixtures/directory/nested/nestedfile.txt'",
+    "writing \\"directory/directoryfile.txt\\" to compilation assets from \\"./fixtures/directory/directoryfile.txt\\"",
+    "writing \\"directory/nested/deep-nested/deepnested.txt\\" to compilation assets from \\"./fixtures/directory/nested/deep-nested/deepnested.txt\\"",
+    "writing \\"directory/nested/nestedfile.txt\\" to compilation assets from \\"./fixtures/directory/nested/nestedfile.txt\\"",
   ],
 }
 `;
diff --git a/test/from-option.test.js b/test/from-option.test.js
index 0ddc678..2762f99 100644
--- a/test/from-option.test.js
+++ b/test/from-option.test.js
@@ -71,7 +71,7 @@ describe('from option', () => {
           process.platform === 'win32'
             ? [
                 new Error(
-                  `unable to locate 'symlink${path.sep}file-ln.txt' at '${FIXTURES_DIR}${path.sep}symlink${path.sep}file-ln.txt'`
+                  `unable to locate "symlink${path.sep}file-ln.txt" at "${FIXTURES_DIR}${path.sep}symlink${path.sep}file-ln.txt"`
                 ),
               ]
             : [],
@@ -91,7 +91,7 @@ describe('from option', () => {
         expectedAssetKeys: [],
         expectedErrors: [
           new Error(
-            `unable to locate 'nonexistent.txt' at '${FIXTURES_DIR}${path.sep}nonexistent.txt'`
+            `unable to locate "nonexistent.txt" at "${FIXTURES_DIR}${path.sep}nonexistent.txt"`
           ),
         ],
         patterns: [
@@ -210,7 +210,7 @@ describe('from option', () => {
           process.platform === 'win32'
             ? [
                 new Error(
-                  `unable to locate 'symlink${path.sep}directory-ln' at '${FIXTURES_DIR}${path.sep}symlink${path.sep}directory-ln\\**\\*'`
+                  `unable to locate "symlink${path.sep}directory-ln" at "${FIXTURES_DIR}${path.sep}symlink${path.sep}directory-ln\\**\\*"`
                 ),
               ]
             : [],
@@ -295,7 +295,7 @@ describe('from option', () => {
         expectedAssetKeys: [],
         expectedErrors: [
           new Error(
-            `unable to locate 'nonexistent' at '${FIXTURES_DIR}${path.sep}nonexistent'`
+            `unable to locate "nonexistent" at "${FIXTURES_DIR}${path.sep}nonexistent"`
           ),
         ],
         patterns: [
@@ -475,7 +475,7 @@ describe('from option', () => {
           process.platform === 'win32'
             ? [
                 new Error(
-                  `unable to locate 'symlink\\**\\*.txt' at '${FIXTURES_DIR}${path.sep}symlink\\**\\*.txt'`
+                  `unable to locate "symlink\\**\\*.txt" at "${FIXTURES_DIR}${path.sep}symlink\\**\\*.txt"`
                 ),
               ]
             : [],
@@ -505,7 +505,7 @@ describe('from option', () => {
         expectedAssetKeys: [],
         expectedErrors: [
           new Error(
-            `unable to locate 'nonexistent${path.sep}**${path.sep}*' at '${FIXTURES_DIR}${path.sep}nonexistent${path.sep}**${path.sep}*'`
+            `unable to locate "nonexistent${path.sep}**${path.sep}*" at "${FIXTURES_DIR}${path.sep}nonexistent${path.sep}**${path.sep}*"`
           ),
         ],
         patterns: [
diff --git a/test/globOptions-option.test.js b/test/globOptions-option.test.js
index 595ca35..7eab8b4 100644
--- a/test/globOptions-option.test.js
+++ b/test/globOptions-option.test.js
@@ -78,7 +78,7 @@ describe('globOptions option', () => {
     runEmit({
       expectedErrors: [
         new Error(
-          `unable to locate 'file.txt' at '${FIXTURES_DIR}${path.sep}file.txt'`
+          `unable to locate "file.txt" at "${FIXTURES_DIR}${path.sep}file.txt"`
         ),
       ],
       patterns: [
@@ -255,7 +255,7 @@ describe('globOptions option', () => {
     runEmit({
       expectedErrors: [
         new Error(
-          `unable to locate 'directory' at '${FIXTURES_DIR}${path.sep}directory${path.sep}**${path.sep}*'`
+          `unable to locate "directory" at "${FIXTURES_DIR}${path.sep}directory${path.sep}**${path.sep}*"`
         ),
       ],
       patterns: [
@@ -275,7 +275,7 @@ describe('globOptions option', () => {
     runEmit({
       expectedErrors: [
         new Error(
-          `unable to locate 'file.txt' at '${FIXTURES_DIR}${path.sep}file.txt'`
+          `unable to locate "file.txt" at "${FIXTURES_DIR}${path.sep}file.txt"`
         ),
       ],
       patterns: [
diff --git a/test/toType-option.test.js b/test/toType-option.test.js
index 7940b92..bcef65f 100644
--- a/test/toType-option.test.js
+++ b/test/toType-option.test.js
@@ -88,7 +88,7 @@ describe('toType option', () => {
       expectedAssetKeys: [],
       expectedErrors: [
         new Error(
-          `unable to locate 'nonexistent.txt' at '${FIXTURES_DIR}${path.sep}nonexistent.txt'`
+          `unable to locate "nonexistent.txt" at "${FIXTURES_DIR}${path.sep}nonexistent.txt"`
         ),
       ],
       patterns: [