Skip to content
This repository was archived by the owner on Sep 7, 2026. It is now read-only.
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,72 @@

Copies existing individual files or entire directories to the build directory.

## Deprecated

> [!WARNING]
>
> This plugin is deprecated. webpack copies files itself since **5.111.0**, through the [`output.copy`](https://webpack.js.org/configuration/output/#outputcopy) option and the `webpack.CopyPlugin` behind it, so this package is no longer needed there. It stays published and keeps working, and it remains the answer for webpack 5 releases older than 5.111.0, but new options land in webpack rather than here.

Using a webpack that has it, this is the whole migration:

```diff
- const CopyPlugin = require("copy-webpack-plugin");
-
module.exports = {
- plugins: [
- new CopyPlugin({
- patterns: [{ from: "static", to: "public" }],
- }),
- ],
+ output: {
+ copy: [{ from: "static", to: "public" }],
+ },
};
```

`output.copy` takes the patterns directly — a single string is one of them, so `copy: "static"` is a whole configuration. `concurrency` and the `processAssets` `stage` live on the plugin instead, for the builds that set them:

```js
const { Compilation, CopyPlugin } = require("webpack");

module.exports = {
plugins: [
new CopyPlugin({
patterns: ["static"],
concurrency: 50,
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
}),
],
};
```

`stage` decides which asset-processing taps see the copied files. It is not how a file is kept out of the minimizer — that one re-runs for assets added at any later stage, so `info: { minimized: true }` is still the way, exactly as it is here.

A [worked example](https://github.com/webpack/webpack/tree/main/examples/output-copy) covers both forms, the pattern options and that second pass.

### What each option becomes

| This plugin | `output.copy` |
| --------------------- | ---------------------------------------------------------------------------------- |
| `patterns` | `output.copy` itself |
| `from` | `from` |
| `to` | `to`, which may be a function of the copied file |
| `context` | `context` |
| `globOptions` | `globOptions` — `caseSensitive`, `deep`, `dot`, `followSymlinks`, `ignore` |
| `info` | `info` |
| `transform` | `transform` |
| `toType` | read from `to`; use `filename` for a template |
| `filter` | `globOptions.ignore` |
| `noErrorOnMissing` | the default — a pattern matching nothing warns rather than fails |
| `options.concurrency` | `new CopyPlugin({ concurrency })` |
| `priority` | patterns are applied in order, and a later one replaces what an earlier one copied |
| `force` | no equivalent: copying onto an asset the compilation emits is an error |
| `transformAll` | no equivalent — see below |

Three pattern options have no counterpart here, because webpack grew them rather than inheriting them: `filename` (a webpack filename template, so one pattern can rename, flatten and hash), `preservePermissions` and `preserveTimestamps`.

`transformAll` merged several sources into one asset, which `output.copy` deliberately does not do — one source file becomes one asset there, so merging is a second pass over what it emitted. The [example](https://github.com/webpack/webpack/tree/main/examples/output-copy) carries that second pass as a ~30-line plugin, caching included.

## Getting Started

To begin, you'll need to install `copy-webpack-plugin`:
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,36 @@ const getTinyGlobby = memoize(() => require("tinyglobby"));

const PLUGIN_NAME = "CopyPlugin";

const DEPRECATION_MESSAGE = `copy-webpack-plugin is deprecated: webpack copies files itself since 5.111.0, through the 'output.copy' option and the 'webpack.CopyPlugin' behind it.
Migration guide: https://github.com/webpack/copy-webpack-plugin#deprecated
Example: https://github.com/webpack/webpack/tree/main/examples/output-copy`;

let deprecationWarned = false;

/**
* Warns once per process that webpack copies files on its own. A webpack too
* old to do it has nothing to migrate to, so it is left alone.
* @param {Compiler} compiler the compiler
* @returns {void}
*/
function warnDeprecated(compiler) {
if (
deprecationWarned ||
!compiler.webpack ||
!("CopyPlugin" in compiler.webpack)
) {
return;
}

deprecationWarned = true;

process.emitWarning(
DEPRECATION_MESSAGE,
"DeprecationWarning",
"DEP_COPY_WEBPACK_PLUGIN",
);
}

class CopyPlugin {
/**
* @param {PluginOptions=} options options for the plugin
Expand Down Expand Up @@ -819,6 +849,8 @@ class CopyPlugin {
apply(compiler) {
const pluginName = this.constructor.name;

warnDeprecated(compiler);

compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
const logger = compilation.getLogger("copy-webpack-plugin");
const cache = compilation.getCache("CopyWebpackPlugin");
Expand Down
83 changes: 83 additions & 0 deletions test/deprecation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { getCompiler } from "./helpers";

// the flag which makes the warning fire once lives in the module, so each case
// needs its own copy of it
const loadPlugin = () => {
let CopyPlugin;

jest.isolateModules(() => {
CopyPlugin = require("../src").default || require("../src");
});

return CopyPlugin;
};

// only `Compilation` and `CopyPlugin` are read from it, and building it by
// hand keeps webpack's own deprecated getters out of the output
const namespace = (extra) => ({
Compilation: require("webpack").Compilation,
...extra,
});

const applyTo = (webpackNamespace) => {
const CopyPlugin = loadPlugin();
const compiler = getCompiler();

compiler.webpack = webpackNamespace;

new CopyPlugin({ patterns: ["directory"] }).apply(compiler);

return compiler;
};

describe("deprecation", () => {
let emitWarning;

beforeEach(() => {
emitWarning = jest.spyOn(process, "emitWarning").mockImplementation();
});

afterEach(() => {
emitWarning.mockRestore();
});

it("should warn when webpack copies files itself", () => {
applyTo(namespace({ CopyPlugin: class {} }));

expect(emitWarning).toHaveBeenCalledTimes(1);

const [[message, type, code]] = emitWarning.mock.calls;

expect(message).toMatch("copy-webpack-plugin is deprecated");
expect(message).toMatch("5.111.0");
expect(message).toMatch("output.copy");
expect(type).toBe("DeprecationWarning");
expect(code).toBe("DEP_COPY_WEBPACK_PLUGIN");
});

it("should warn once for several compilers", () => {
const CopyPlugin = loadPlugin();
const webpackNamespace = namespace({ CopyPlugin: class {} });

for (const _ of [0, 1, 2]) {
const compiler = getCompiler();

compiler.webpack = webpackNamespace;
new CopyPlugin({ patterns: ["directory"] }).apply(compiler);
}

expect(emitWarning).toHaveBeenCalledTimes(1);
});

it("should not warn when webpack cannot copy files itself", () => {
applyTo(namespace());

expect(emitWarning).not.toHaveBeenCalled();
});

it("should not warn when the compiler has no webpack namespace", () => {
applyTo(undefined);

expect(emitWarning).not.toHaveBeenCalled();
});
});
Loading