Repository: webpack/webpack
Pull request #7611 (2018-06-27 09:20:01)

Do not set nodeEnv when 'mode=none'
When `mode=none`, `nodeEnv` optimization is now disabled.

**What kind of change does this PR introduce?**

refactoring

**Did you add tests for your changes?**

yes

**Does this PR introduce a breaking change?**

yes

**What needs to be documented once your changes are merged?**

`mode=none` now sets `optimization.nodeEnv=false`



Patch:
diff --git a/.travis.yml b/.travis.yml
index 208440d70..184cf5ee2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -40,6 +40,10 @@ matrix:
       node_js: "8"
       env: NO_WATCH_TESTS=1 JEST="--maxWorkers=2 --cacheDirectory .jest-cache" JOB_PART=integration
       stage: versions
+    - os: linux
+      node_js: "6"
+      env: NO_WATCH_TESTS=1 JEST="--maxWorkers=2 --cacheDirectory .jest-cache" JOB_PART=integration
+      stage: versions
   fast_finish: true
   allow_failures:
     - os: osx
diff --git a/appveyor.yml b/appveyor.yml
index db4d6e685..bd61e1135 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -22,7 +22,7 @@ environment:
     - nodejs_version: 10
       job_part: integration
       jest: --maxWorkers=2 --cacheDirectory .jest-cache
-    - nodejs_version: 8
+    - nodejs_version: 6
       job_part: integration
       jest: --maxWorkers=2 --cacheDirectory .jest-cache
 
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 000000000..769cc7252
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,23 @@
+machine:
+  environment:
+    NO_WATCH_TESTS: 1
+
+machine:
+  environment:
+    PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
+
+dependencies:
+  pre:
+    - case $CIRCLE_NODE_INDEX in 0) NODE_VERSION=6 ;; 1) NODE_VERSION=8 ;; esac; nvm install $NODE_VERSION && nvm alias default $NODE_VERSION
+  override:
+    - yarn
+    - yarn link || true && yarn link webpack
+  cache_directories:
+    - ~/.cache/yarn
+
+test:
+  override:
+    - node -v
+    - yarn --version
+    - yarn run circleci:lint
+    - yarn run circleci:test
diff --git a/lib/AsyncDependenciesBlock.js b/lib/AsyncDependenciesBlock.js
index 2dc26f91c..3bf22d2e2 100644
--- a/lib/AsyncDependenciesBlock.js
+++ b/lib/AsyncDependenciesBlock.js
@@ -28,6 +28,14 @@ module.exports = class AsyncDependenciesBlock extends DependenciesBlock {
 		this.groupOptions.name = value;
 	}
 
+	get chunks() {
+		throw new Error("Moved to AsyncDependenciesBlock.chunkGroup");
+	}
+
+	set chunks(value) {
+		throw new Error("Moved to AsyncDependenciesBlock.chunkGroup");
+	}
+
 	updateHash(hash) {
 		hash.update(JSON.stringify(this.groupOptions));
 		hash.update(
diff --git a/lib/Chunk.js b/lib/Chunk.js
index a92c611af..774eb445a 100644
--- a/lib/Chunk.js
+++ b/lib/Chunk.js
@@ -4,11 +4,14 @@ Author Tobias Koppers @sokra
 */
 "use strict";
 
+const util = require("util");
 const SortableSet = require("./util/SortableSet");
 const intersect = require("./util/SetHelpers").intersect;
 const GraphHelpers = require("./GraphHelpers");
-
 let debugId = 1000;
+const ERR_CHUNK_ENTRY = "Chunk.entry was removed. Use hasRuntime()";
+const ERR_CHUNK_INITIAL =
+	"Chunk.initial was removed. Use canBeInitial/isOnlyInitial()";
 
 /** @typedef {import("./Module.js")} Module */
 /** @typedef {import("./ChunkGroup")} ChunkGroup */
@@ -130,6 +133,40 @@ class Chunk {
 		this.removedModules = undefined;
 	}
 
+	/**
+	 * @deprecated Chunk.entry has been deprecated. Please use .hasRuntime() instead
+	 * @returns {never} Throws an error trying to access this property
+	 */
+	get entry() {
+		throw new Error(ERR_CHUNK_ENTRY);
+	}
+
+	/**
+	 * @deprecated .entry has been deprecated. Please use .hasRuntime() instead
+	 * @param {never} data The data that was attempting to be set
+	 * @returns {never} Throws an error trying to access this property
+	 */
+	set entry(data) {
+		throw new Error(ERR_CHUNK_ENTRY);
+	}
+
+	/**
+	 * @deprecated Chunk.initial was removed. Use canBeInitial/isOnlyInitial()
+	 * @returns {never} Throws an error trying to access this property
+	 */
+	get initial() {
+		throw new Error(ERR_CHUNK_INITIAL);
+	}
+
+	/**
+	 * @deprecated Chunk.initial was removed. Use canBeInitial/isOnlyInitial()
+	 * @param {never} data The data attempting to be set
+	 * @returns {never} Throws an error trying to access this property
+	 */
+	set initial(data) {
+		throw new Error(ERR_CHUNK_INITIAL);
+	}
+
 	/**
 	 * @returns {boolean} whether or not the Chunk will have a runtime
 	 */
@@ -659,4 +696,66 @@ class Chunk {
 	}
 }
 
+// TODO remove in webpack 5
+Object.defineProperty(Chunk.prototype, "forEachModule", {
+	configurable: false,
+	value: util.deprecate(function(fn) {
+		this._modules.forEach(fn);
+	}, "Chunk.forEachModule: Use for(const module of chunk.modulesIterable) instead")
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(Chunk.prototype, "mapModules", {
+	configurable: false,
+	value: util.deprecate(function(fn) {
+		return Array.from(this._modules, fn);
+	}, "Chunk.mapModules: Use Array.from(chunk.modulesIterable, fn) instead")
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(Chunk.prototype, "chunks", {
+	configurable: false,
+	get() {
+		throw new Error("Chunk.chunks: Use ChunkGroup.getChildren() instead");
+	},
+	set() {
+		throw new Error("Chunk.chunks: Use ChunkGroup.add/removeChild() instead");
+	}
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(Chunk.prototype, "parents", {
+	configurable: false,
+	get() {
+		throw new Error("Chunk.parents: Use ChunkGroup.getParents() instead");
+	},
+	set() {
+		throw new Error("Chunk.parents: Use ChunkGroup.add/removeParent() instead");
+	}
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(Chunk.prototype, "blocks", {
+	configurable: false,
+	get() {
+		throw new Error("Chunk.blocks: Use ChunkGroup.getBlocks() instead");
+	},
+	set() {
+		throw new Error("Chunk.blocks: Use ChunkGroup.add/removeBlock() instead");
+	}
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(Chunk.prototype, "entrypoints", {
+	configurable: false,
+	get() {
+		throw new Error(
+			"Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead"
+		);
+	},
+	set() {
+		throw new Error("Chunk.entrypoints: Use Chunks.addGroup instead");
+	}
+});
+
 module.exports = Chunk;
diff --git a/lib/Compilation.js b/lib/Compilation.js
index e870f4627..fffd946e3 100644
--- a/lib/Compilation.js
+++ b/lib/Compilation.js
@@ -5,6 +5,7 @@
 "use strict";
 
 const asyncLib = require("neo-async");
+const util = require("util");
 const { CachedSource } = require("webpack-sources");
 const {
 	SyncHook,
@@ -1914,4 +1915,22 @@ class Compilation {
 	}
 }
 
+// TODO remove in webpack 5
+Compilation.prototype.applyPlugins = util.deprecate(function(name, ...args) {
+	this.hooks[
+		name.replace(/[- ]([a-z])/g, match => match[1].toUpperCase())
+	].call(...args);
+}, "Compilation.applyPlugins is deprecated. Use new API on `.hooks` instead");
+
+// TODO remove in webpack 5
+Object.defineProperty(Compilation.prototype, "moduleTemplate", {
+	configurable: false,
+	get: util.deprecate(function() {
+		return this.moduleTemplates.javascript;
+	}, "Compilation.moduleTemplate: Use Compilation.moduleTemplates.javascript instead"),
+	set: util.deprecate(function(value) {
+		this.moduleTemplates.javascript = value;
+	}, "Compilation.moduleTemplate: Use Compilation.moduleTemplates.javascript instead.")
+});
+
 module.exports = Compilation;
diff --git a/lib/ContextModule.js b/lib/ContextModule.js
index 0ea336138..175187a69 100644
--- a/lib/ContextModule.js
+++ b/lib/ContextModule.js
@@ -3,8 +3,8 @@
 	Author Tobias Koppers @sokra
 */
 "use strict";
-
 const path = require("path");
+const util = require("util");
 const { OriginalSource, RawSource } = require("webpack-sources");
 const Module = require("./Module");
 const AsyncDependenciesBlock = require("./AsyncDependenciesBlock");
@@ -693,4 +693,59 @@ webpackEmptyAsyncContext.id = ${JSON.stringify(id)};`;
 	}
 }
 
+// TODO remove in webpack 5
+Object.defineProperty(ContextModule.prototype, "recursive", {
+	configurable: false,
+	get: util.deprecate(function() {
+		return this.options.recursive;
+	}, "ContextModule.recursive has been moved to ContextModule.options.recursive"),
+	set: util.deprecate(function(value) {
+		this.options.recursive = value;
+	}, "ContextModule.recursive has been moved to ContextModule.options.recursive")
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(ContextModule.prototype, "regExp", {
+	configurable: false,
+	get: util.deprecate(function() {
+		return this.options.regExp;
+	}, "ContextModule.regExp has been moved to ContextModule.options.regExp"),
+	set: util.deprecate(function(value) {
+		this.options.regExp = value;
+	}, "ContextModule.regExp has been moved to ContextModule.options.regExp")
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(ContextModule.prototype, "addon", {
+	configurable: false,
+	get: util.deprecate(function() {
+		return this.options.addon;
+	}, "ContextModule.addon has been moved to ContextModule.options.addon"),
+	set: util.deprecate(function(value) {
+		this.options.addon = value;
+	}, "ContextModule.addon has been moved to ContextModule.options.addon")
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(ContextModule.prototype, "async", {
+	configurable: false,
+	get: util.deprecate(function() {
+		return this.options.mode;
+	}, "ContextModule.async has been moved to ContextModule.options.mode"),
+	set: util.deprecate(function(value) {
+		this.options.mode = value;
+	}, "ContextModule.async has been moved to ContextModule.options.mode")
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(ContextModule.prototype, "chunkName", {
+	configurable: false,
+	get: util.deprecate(function() {
+		return this.options.chunkName;
+	}, "ContextModule.chunkName has been moved to ContextModule.options.chunkName"),
+	set: util.deprecate(function(value) {
+		this.options.chunkName = value;
+	}, "ContextModule.chunkName has been moved to ContextModule.options.chunkName")
+});
+
 module.exports = ContextModule;
diff --git a/lib/Module.js b/lib/Module.js
index 4bbb8ee9e..4341414ac 100644
--- a/lib/Module.js
+++ b/lib/Module.js
@@ -4,6 +4,8 @@
 */
 "use strict";
 
+const util = require("util");
+
 const DependenciesBlock = require("./DependenciesBlock");
 const ModuleReason = require("./ModuleReason");
 const SortableSet = require("./util/SortableSet");
@@ -320,8 +322,54 @@ class Module extends DependenciesBlock {
 		this.buildInfo = undefined;
 		this.disconnect();
 	}
+
+	get arguments() {
+		throw new Error("Module.arguments was removed, there is no replacement.");
+	}
+
+	set arguments(value) {
+		throw new Error("Module.arguments was removed, there is no replacement.");
+	}
 }
 
+// TODO remove in webpack 5
+Object.defineProperty(Module.prototype, "forEachChunk", {
+	configurable: false,
+	value: util.deprecate(function(fn) {
+		this._chunks.forEach(fn);
+	}, "Module.forEachChunk: Use for(const chunk of module.chunksIterable) instead")
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(Module.prototype, "mapChunks", {
+	configurable: false,
+	value: util.deprecate(function(fn) {
+		return Array.from(this._chunks, fn);
+	}, "Module.mapChunks: Use Array.from(module.chunksIterable, fn) instead")
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(Module.prototype, "entry", {
+	configurable: false,
+	get() {
+		throw new Error("Module.entry was removed. Use Chunk.entryModule");
+	},
+	set() {
+		throw new Error("Module.entry was removed. Use Chunk.entryModule");
+	}
+});
+
+// TODO remove in webpack 5
+Object.defineProperty(Module.prototype, "meta", {
+	configurable: false,
+	get: util.deprecate(function() {
+		return this.buildMeta;
+	}, "Module.meta was renamed to Module.buildMeta"),
+	set: util.deprecate(function(value) {
+		this.buildMeta = value;
+	}, "Module.meta was renamed to Module.buildMeta")
+});
+
 /** @type {function(): string} */
 Module.prototype.identifier = null;
 
diff --git a/lib/Parser.js b/lib/Parser.js
index 2b563bb4b..576e3f834 100644
--- a/lib/Parser.js
+++ b/lib/Parser.js
@@ -8,6 +8,7 @@
 
 const acorn = require("acorn-dynamic-import").default;
 const { SyncBailHook, HookMap } = require("tapable");
+const util = require("util");
 const vm = require("vm");
 const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
 const StackedSetMap = require("./util/StackedSetMap");
@@ -2136,4 +2137,12 @@ class Parser {
 	}
 }
 
+// TODO remove in webpack 5
+Object.defineProperty(Parser.prototype, "getCommentOptions", {
+	configurable: false,
+	value: util.deprecate(function(range) {
+		return this.parseCommentOptions(range).options;
+	}, "Parser.getCommentOptions: Use Parser.parseCommentOptions(range) instead")
+});
+
 module.exports = Parser;
diff --git a/lib/RemovedPluginError.js b/lib/RemovedPluginError.js
new file mode 100644
index 000000000..626c3b4fb
--- /dev/null
+++ b/lib/RemovedPluginError.js
@@ -0,0 +1,11 @@
+"use strict";
+
+const WebpackError = require("./WebpackError");
+
+module.exports = class RemovedPluginError extends WebpackError {
+	constructor(message) {
+		super(message);
+
+		Error.captureStackTrace(this, this.constructor);
+	}
+};
diff --git a/lib/WebpackOptionsDefaulter.js b/lib/WebpackOptionsDefaulter.js
index 0e2d8fbe6..8f80f6d9b 100644
--- a/lib/WebpackOptionsDefaulter.js
+++ b/lib/WebpackOptionsDefaulter.js
@@ -299,8 +299,11 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
 			}
 		]);
 		this.set("optimization.nodeEnv", "make", options => {
-			// TODO: In webpack 5, it should return `false` when mode is `none`
-			return options.mode || "production";
+			if (options.mode === "none") {
+				return false;
+			} else {
+				return options.mode || "production";
+			}
 		});
 
 		this.set("resolve", "call", value => Object.assign({}, value));
diff --git a/lib/dependencies/ContextDependency.js b/lib/dependencies/ContextDependency.js
index dac077572..10c2fea99 100644
--- a/lib/dependencies/ContextDependency.js
+++ b/lib/dependencies/ContextDependency.js
@@ -50,4 +50,19 @@ class ContextDependency extends Dependency {
 	}
 }
 
+// TODO remove in webpack 5
+Object.defineProperty(ContextDependency.prototype, "async", {
+	configurable: false,
+	get() {
+		throw new Error(
+			"ContextDependency.async was removed. Use ContextDependency.options.mode instead."
+		);
+	},
+	set() {
+		throw new Error(
+			"ContextDependency.async was removed. Pass options.mode to constructor instead"
+		);
+	}
+});
+
 module.exports = ContextDependency;
diff --git a/lib/util/StackedSetMap.js b/lib/util/StackedSetMap.js
index f602146a9..589b68d52 100644
--- a/lib/util/StackedSetMap.js
+++ b/lib/util/StackedSetMap.js
@@ -4,6 +4,8 @@
 */
 "use strict";
 
+const util = require("util");
+
 const TOMBSTONE = {};
 const UNDEFINED_MARKER = {};
 
@@ -115,6 +117,19 @@ class StackedSetMap {
 	createChild() {
 		return new StackedSetMap(this.stack);
 	}
+
+	get length() {
+		throw new Error("This is no longer an Array");
+	}
+
+	set length(value) {
+		throw new Error("This is no longer an Array");
+	}
 }
 
+// TODO remove in webpack 5
+StackedSetMap.prototype.push = util.deprecate(function(item) {
+	this.add(item);
+}, "This is no longer an Array: Use add instead.");
+
 module.exports = StackedSetMap;
diff --git a/lib/webpack.js b/lib/webpack.js
index fdafae4e7..facfebf6c 100644
--- a/lib/webpack.js
+++ b/lib/webpack.js
@@ -12,6 +12,7 @@ const WebpackOptionsDefaulter = require("./WebpackOptionsDefaulter");
 const validateSchema = require("./validateSchema");
 const WebpackOptionsValidationError = require("./WebpackOptionsValidationError");
 const webpackOptionsSchema = require("../schemas/WebpackOptions.json");
+const RemovedPluginError = require("./RemovedPluginError");
 const version = require("../package.json").version;
 
 const webpack = (options, callback) => {
@@ -156,3 +157,27 @@ exportPlugins((exports.debug = {}), {
 exportPlugins((exports.util = {}), {
 	createHash: () => require("./util/createHash")
 });
+
+const defineMissingPluginError = (namespace, pluginName, errorMessage) => {
+	Object.defineProperty(namespace, pluginName, {
+		configurable: false,
+		enumerable: true,
+		get() {
+			throw new RemovedPluginError(errorMessage);
+		}
+	});
+};
+
+// TODO remove in webpack 5
+defineMissingPluginError(
+	exports.optimize,
+	"UglifyJsPlugin",
+	"webpack.optimize.UglifyJsPlugin has been removed, please use config.optimization.minimize instead."
+);
+
+// TODO remove in webpack 5
+defineMissingPluginError(
+	exports.optimize,
+	"CommonsChunkPlugin",
+	"webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead."
+);
diff --git a/package.json b/package.json
index dac3277ad..76f95b5e9 100644
--- a/package.json
+++ b/package.json
@@ -81,7 +81,7 @@
     "xxhashjs": "^0.2.1"
   },
   "engines": {
-    "node": ">=8.9.0"
+    "node": ">=6.11.5"
   },
   "repository": {
     "type": "git",


Test patch:
diff --git a/test/Chunk.unittest.js b/test/Chunk.unittest.js
index af34250c6..dab229ee6 100644
--- a/test/Chunk.unittest.js
+++ b/test/Chunk.unittest.js
@@ -22,6 +22,34 @@ describe("Chunk", () => {
 		expect(ChunkInstance.canBeInitial()).toBe(false);
 	});
 
+	describe("entry", () => {
+		it("returns an error if get entry", () => {
+			expect(() => {
+				ChunkInstance.entry;
+			}).toThrow("Chunk.entry was removed. Use hasRuntime()");
+		});
+
+		it("returns an error if set an entry", () => {
+			expect(() => {
+				ChunkInstance.entry = 10;
+			}).toThrow("Chunk.entry was removed. Use hasRuntime()");
+		});
+	});
+
+	describe("initial", () => {
+		it("returns an error if get initial", () => {
+			expect(() => {
+				ChunkInstance.initial;
+			}).toThrow("Chunk.initial was removed. Use canBeInitial/isOnlyInitial()");
+		});
+
+		it("returns an error if set an initial", () => {
+			expect(() => {
+				ChunkInstance.initial = 10;
+			}).toThrow("Chunk.initial was removed. Use canBeInitial/isOnlyInitial()");
+		});
+	});
+
 	describe("hasRuntime", () => {
 		it("returns false", () => {
 			expect(ChunkInstance.hasRuntime()).toBe(false);
diff --git a/test/RemovedPlugins.unittest.js b/test/RemovedPlugins.unittest.js
new file mode 100644
index 000000000..915ee0e11
--- /dev/null
+++ b/test/RemovedPlugins.unittest.js
@@ -0,0 +1,18 @@
+const webpack = require("../lib/webpack");
+const RemovedPluginError = require("../lib/RemovedPluginError");
+
+describe("removed plugin errors", () => {
+	it("should error when accessing removed plugins", () => {
+		expect(() => webpack.optimize.UglifyJsPlugin).toThrow(RemovedPluginError);
+		expect(
+			() => webpack.optimize.UglifyJsPlugin
+		).toThrowErrorMatchingSnapshot();
+
+		expect(() => webpack.optimize.CommonsChunkPlugin).toThrow(
+			RemovedPluginError
+		);
+		expect(
+			() => webpack.optimize.CommonsChunkPlugin
+		).toThrowErrorMatchingSnapshot();
+	});
+});
diff --git a/test/__snapshots__/RemovedPlugins.unittest.js.snap b/test/__snapshots__/RemovedPlugins.unittest.js.snap
new file mode 100644
index 000000000..ecac2f61a
--- /dev/null
+++ b/test/__snapshots__/RemovedPlugins.unittest.js.snap
@@ -0,0 +1,5 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`removed plugin errors should error when accessing removed plugins 1`] = `"webpack.optimize.UglifyJsPlugin has been removed, please use config.optimization.minimize instead."`;
+
+exports[`removed plugin errors should error when accessing removed plugins 2`] = `"webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead."`;
diff --git a/test/configCases/issues/issue-7470/index.js b/test/configCases/issues/issue-7470/index.js
index 6267ef7b1..d47b80b19 100644
--- a/test/configCases/issues/issue-7470/index.js
+++ b/test/configCases/issues/issue-7470/index.js
@@ -1,3 +1,7 @@
 it("should set NODE_ENV according to mode", () => {
-	expect(process.env.NODE_ENV).toBe(__MODE__);
+	if (__MODE__ === "none") {
+		expect(process.env.NODE_ENV).toBe("test");
+	} else {
+		expect(process.env.NODE_ENV).toBe(__MODE__);
+	}
 });