Repository: webpack/webpack (JavaScript)

Issue:
The `webpack.ProvidePlugin` does not tree shake code as documented
# Steps to reproduce this issue

1.  Initialize the project structure:
```bash
$ cd <base>
$ mkdir test
$ cd test
$ yarn init
$ yarn add --dev webpack webpack-cli
$ mkdir src
```
2.  Create `src/index.js`:
```js
function component() {
    const element = document.createElement('pre');
    element.innerHTML = ['Hello', 'webpack', '5 cube is equal to ' + cube(5)].join('\n\n');
    return element;
}
document.body.appendChild(component());
```
3. Create `src/math.js`:
```js
export function square(x) {
    return x * x;
};

export function cube(x) {
    return x * x * x;
};
```

4. Create `webpack.config.js`:
```js
const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
        new webpack.ProvidePlugin({
            cube: ['./math.js', 'cube'],
        }),
    ],
    mode: 'production',
    optimization: {
        runtimeChunk: true,
    },
};

```

5. Build the bundle:
```bash
$ yarn webpack
```

6. Expected Result: 
**ONLY** the `cube()` function is included in `dist/main.bundle.js`

7. Actual Result:
**BOTH** the `cube()` and the `square()` functions are included.

# Environment
* webpack - 5.14.10
* node - 15.5.0
* OS - macOS Big Sur


Hints:
<!-- identifier: inactive-warning -->

**This issue had no activity for at least three months.**

It's subject to automatic issue closing if there is no activity in the next 15 days.
bump
<!-- identifier: inactive-close -->

Issue was closed because of inactivity.

If you think this is still a valid issue, please file a new issue with additional information.
Closing because of inactivity. Feel free to report new issue with reproducible repo.
Still valid

Patch:
diff --git a/lib/dependencies/ProvidedDependency.js b/lib/dependencies/ProvidedDependency.js
--- a/lib/dependencies/ProvidedDependency.js
+++ b/lib/dependencies/ProvidedDependency.js
@@ -5,19 +5,21 @@
 
 "use strict";
 
+const Dependency = require("../Dependency");
 const InitFragment = require("../InitFragment");
 const makeSerializable = require("../util/makeSerializable");
 const ModuleDependency = require("./ModuleDependency");
 
 /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
 /** @typedef {import("../ChunkGraph")} ChunkGraph */
-/** @typedef {import("../Dependency")} Dependency */
+/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
 /** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
 /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
 /** @typedef {import("../DependencyTemplates")} DependencyTemplates */
 /** @typedef {import("../ModuleGraph")} ModuleGraph */
 /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
 /** @typedef {import("../util/Hash")} Hash */
+/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
 
 /**
  * @param {string[]|null} path the property path array
@@ -29,10 +31,16 @@ const pathToString = path =>
 		: "";
 
 class ProvidedDependency extends ModuleDependency {
-	constructor(request, identifier, path, range) {
+	/**
+	 * @param {string} request request
+	 * @param {string} identifier identifier
+	 * @param {string[]} ids ids
+	 * @param {[number, number]} range range
+	 */
+	constructor(request, identifier, ids, range) {
 		super(request);
 		this.identifier = identifier;
-		this.path = path;
+		this.ids = ids;
 		this.range = range;
 		this._hashUpdate = undefined;
 	}
@@ -45,6 +53,18 @@ class ProvidedDependency extends ModuleDependency {
 		return "esm";
 	}
 
+	/**
+	 * Returns list of exports referenced by this dependency
+	 * @param {ModuleGraph} moduleGraph module graph
+	 * @param {RuntimeSpec} runtime the runtime for which the module is analysed
+	 * @returns {(string[] | ReferencedExport)[]} referenced exports
+	 */
+	getReferencedExports(moduleGraph, runtime) {
+		let ids = this.ids;
+		if (ids.length === 0) return Dependency.EXPORTS_OBJECT_REFERENCED;
+		return [ids];
+	}
+
 	/**
 	 * Update the hash
 	 * @param {Hash} hash hash to be updated
@@ -53,8 +73,7 @@ class ProvidedDependency extends ModuleDependency {
 	 */
 	updateHash(hash, context) {
 		if (this._hashUpdate === undefined) {
-			this._hashUpdate =
-				this.identifier + (this.path ? this.path.join(",") : "null");
+			this._hashUpdate = this.identifier + (this.ids ? this.ids.join(",") : "");
 		}
 		hash.update(this._hashUpdate);
 	}
@@ -62,14 +81,14 @@ class ProvidedDependency extends ModuleDependency {
 	serialize(context) {
 		const { write } = context;
 		write(this.identifier);
-		write(this.path);
+		write(this.ids);
 		super.serialize(context);
 	}
 
 	deserialize(context) {
 		const { read } = context;
 		this.identifier = read();
-		this.path = read();
+		this.ids = read();
 		super.deserialize(context);
 	}
 }
@@ -90,6 +109,7 @@ class ProvidedDependencyTemplate extends ModuleDependency.Template {
 		dependency,
 		source,
 		{
+			runtime,
 			runtimeTemplate,
 			moduleGraph,
 			chunkGraph,
@@ -98,6 +118,9 @@ class ProvidedDependencyTemplate extends ModuleDependency.Template {
 		}
 	) {
 		const dep = /** @type {ProvidedDependency} */ (dependency);
+		const connection = moduleGraph.getConnection(dep);
+		const exportsInfo = moduleGraph.getExportsInfo(connection.module);
+		const usedName = exportsInfo.getUsedName(dep.ids, runtime);
 		initFragments.push(
 			new InitFragment(
 				`/* provided dependency */ var ${
@@ -107,7 +130,7 @@ class ProvidedDependencyTemplate extends ModuleDependency.Template {
 					chunkGraph,
 					request: dep.request,
 					runtimeRequirements
-				})}${pathToString(dep.path)};\n`,
+				})}${pathToString(/** @type {string[]} */ (usedName))};\n`,
 				InitFragment.STAGE_PROVIDES,
 				1,
 				`provided ${dep.identifier}`


Test patch:
diff --git a/test/configCases/plugins/provide-plugin/a.js b/test/configCases/plugins/provide-plugin/a.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/plugins/provide-plugin/a.js
@@ -0,0 +1,2 @@
+export * as c from "./b";
+export * as c2 from "./harmony2";
diff --git a/test/configCases/plugins/provide-plugin/b.js b/test/configCases/plugins/provide-plugin/b.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/plugins/provide-plugin/b.js
@@ -0,0 +1,7 @@
+export function square(x) {
+	return x * x;
+}
+
+export function cube(x) {
+	return x * x * x;
+}
diff --git a/test/configCases/plugins/provide-plugin/harmony2.js b/test/configCases/plugins/provide-plugin/harmony2.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/plugins/provide-plugin/harmony2.js
@@ -0,0 +1,2 @@
+export const a = 1;
+export const aUsed = __webpack_exports_info__.a.used;
diff --git a/test/configCases/plugins/provide-plugin/index.js b/test/configCases/plugins/provide-plugin/index.js
--- a/test/configCases/plugins/provide-plugin/index.js
+++ b/test/configCases/plugins/provide-plugin/index.js
@@ -48,6 +48,11 @@ it("should provide a module for a property request", function() {
 	expect(x).toBe("fff");
 });
 
+it("should tree-shake unused exports", function() {
+	expect(aa1(2)).toBe(8);
+	expect(es2015_aUsed).toBe(false);
+});
+
 it("should provide ES2015 modules", function() {
 	expect((es2015.default)).toBe("ECMAScript 2015");
 	expect((es2015.alias)).toBe("ECMAScript Harmony");
diff --git a/test/configCases/plugins/provide-plugin/webpack.config.js b/test/configCases/plugins/provide-plugin/webpack.config.js
--- a/test/configCases/plugins/provide-plugin/webpack.config.js
+++ b/test/configCases/plugins/provide-plugin/webpack.config.js
@@ -6,6 +6,8 @@ module.exports = {
 			aaa: "./aaa",
 			"bbb.ccc": "./bbbccc",
 			dddeeefff: ["./ddd", "eee", "3-f"],
+			aa1: ["./a", "c", "cube"],
+			es2015_aUsed: ["./harmony2", "aUsed"],
 			"process.env.NODE_ENV": "./env",
 			es2015: "./harmony",
 			es2015_name: ["./harmony", "default"],