Repository: webpack/webpack
Pull request #6369 (2018-01-23 20:45:59)

move source generation into separate module
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**What kind of change does this PR introduce?**
refactoring
<!-- E.g. a bugfix, feature, refactoring, build related change, etc… -->

**Did you add tests for your changes?**
existing tests
<!-- Note that we won't merge your changes if you don't add tests -->

**If relevant, link to documentation update:**
N/A
<!-- Link PR from webpack/webpack.js.org here, or N/A -->

**Summary**
move source generation into separate module
allow to pass a Generator to NormalModule
change NormalModule constructor to options object
<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->
<!-- Try to link to an open issue for more information. -->

**Does this PR introduce a breaking change?**
yes
<!-- If this PR introduces a breaking change, please describe the impact and a migration path for existing applications. -->

**Other information**
cc @michael-ciniawsky

webpack-bot: <!-- identifier: ci-result -->

@sokra Please review the following output log for errors:

``` text
  1 failing

  1) NormalModule "before each" hook for "returns an identifier for this module":
     TypeError: Cannot read property 'startsWith' of undefined
      at new NormalModule (/home/travis/build/webpack/webpack/lib/NormalModule.js:9:3842)
      at Context.<anonymous> (/home/travis/build/webpack/webpack/test/NormalModule.unittest.js:28:18)
      at callFn (/home/travis/build/webpack/webpack/node_modules/mocha/lib/runnable.js:348:21)
      at Hook.Runnable.run (/home/travis/build/webpack/webpack/node_modules/mocha/lib/runnable.js:340:7)
      at next (/home/travis/build/webpack/webpack/node_modules/mocha/lib/runner.js:309:10)
      at Immediate.<anonymous> (/home/travis/build/webpack/webpack/node_modules/mocha/lib/runner.js:339:5)
      at runCallback (timers.js:789:20)
      at tryOnImmediate (timers.js:751:5)
      at processImmediate [as _immediateCallback] (timers.js:722:5)
```

Instead of updating these (outdated?) unit tests, you can choose to delete them and add integration tests instead. That would be great.

See [complete report here](https://travis-ci.org/webpack/webpack/builds/332491470?utm_source&#x3D;github_status&amp;utm_medium&#x3D;notification).
TheLarkInn: The API for this is a bit verbose, however I think it does serve the needs that we have for now, and lays the foundation for x-as-module type that's non-js-like.

Patch:
diff --git a/lib/JavascriptGenerator.js b/lib/JavascriptGenerator.js
new file mode 100644
index 000000000..19ac2857c
--- /dev/null
+++ b/lib/JavascriptGenerator.js
@@ -0,0 +1,186 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const RawSource = require("webpack-sources").RawSource;
+const ReplaceSource = require("webpack-sources").ReplaceSource;
+
+// TODO: clean up this file
+// replace with newer constructs
+
+// TODO: remove DependencyVariables and replace them with something better
+
+class JavascriptGenerator {
+
+	generate(module, dependencyTemplates, runtimeTemplate) {
+		const originalSource = module.originalSource();
+		if(!originalSource) {
+			return new RawSource("throw new Error('No source available');");
+		}
+
+		const source = new ReplaceSource(originalSource);
+
+		this.sourceBlock(module, module, [], dependencyTemplates, source, runtimeTemplate);
+
+		return source;
+	}
+
+	sourceBlock(module, block, availableVars, dependencyTemplates, source, runtimeTemplate) {
+		for(const dependency of block.dependencies) {
+			this.sourceDependency(dependency, dependencyTemplates, source, runtimeTemplate);
+		}
+
+		/**
+		 * Get the variables of all blocks that we need to inject.
+		 * These will contain the variable name and its expression.
+		 * The name will be added as a paramter in a IIFE the expression as its value.
+		 */
+		const vars = block.variables.reduce((result, value) => {
+			const variable = this.sourceVariables(
+				value, availableVars, dependencyTemplates, runtimeTemplate);
+
+			if(variable) {
+				result.push(variable);
+			}
+
+			return result;
+		}, []);
+
+		/**
+		 * if we actually have variables
+		 * this is important as how #splitVariablesInUniqueNamedChunks works
+		 * it will always return an array in an array which would lead to a IIFE wrapper around
+		 * a module if we do this with an empty vars array.
+		 */
+		if(vars.length > 0) {
+			/**
+			 * Split all variables up into chunks of unique names.
+			 * e.g. imagine you have the following variable names that need to be injected:
+			 * [foo, bar, baz, foo, some, more]
+			 * we can not inject "foo" twice, therefore we just make two IIFEs like so:
+			 * (function(foo, bar, baz){
+			 *   (function(foo, some, more){
+			 *     ...
+			 *   }(...));
+			 * }(...));
+			 *
+			 * "splitVariablesInUniqueNamedChunks" splits the variables shown above up to this:
+			 * [[foo, bar, baz], [foo, some, more]]
+			 */
+			const injectionVariableChunks = this.splitVariablesInUniqueNamedChunks(vars);
+
+			// create all the beginnings of IIFEs
+			const functionWrapperStarts = injectionVariableChunks.map((variableChunk) => {
+				return this.variableInjectionFunctionWrapperStartCode(
+					variableChunk.map(variable => variable.name)
+				);
+			});
+
+			// and all the ends
+			const functionWrapperEnds = injectionVariableChunks.map((variableChunk) => {
+				return this.variableInjectionFunctionWrapperEndCode(
+					module,
+					variableChunk.map(variable => variable.expression), block
+				);
+			});
+
+			// join them to one big string
+			const varStartCode = functionWrapperStarts.join("");
+
+			// reverse the ends first before joining them, as the last added must be the inner most
+			const varEndCode = functionWrapperEnds.reverse().join("");
+
+			// if we have anything, add it to the source
+			if(varStartCode && varEndCode) {
+				const start = block.range ? block.range[0] : -10;
+				const end = block.range ? block.range[1] : (module.originalSource().size() + 1);
+				source.insert(start + 0.5, varStartCode);
+				source.insert(end + 0.5, "\n/* WEBPACK VAR INJECTION */" + varEndCode);
+			}
+		}
+
+		for(const childBlock of block.blocks) {
+			this.sourceBlock(
+				module,
+				childBlock,
+				availableVars.concat(vars),
+				dependencyTemplates,
+				source,
+				runtimeTemplate
+			);
+		}
+	}
+
+	sourceDependency(dependency, dependencyTemplates, source, runtimeTemplate) {
+		const template = dependencyTemplates.get(dependency.constructor);
+		if(!template) throw new Error("No template for dependency: " + dependency.constructor.name);
+		template.apply(dependency, source, runtimeTemplate, dependencyTemplates);
+	}
+
+	sourceVariables(variable, availableVars, dependencyTemplates, runtimeTemplate) {
+		const name = variable.name;
+		const expr = variable.expressionSource(dependencyTemplates, runtimeTemplate);
+
+		if(availableVars.some(v => v.name === name && v.expression.source() === expr.source())) {
+			return;
+		}
+		return {
+			name: name,
+			expression: expr
+		};
+	}
+
+	/*
+	 * creates the start part of a IIFE around the module to inject a variable name
+	 * (function(...){   <- this part
+	 * }.call(...))
+	 */
+	variableInjectionFunctionWrapperStartCode(varNames) {
+		const args = varNames.join(", ");
+		return `/* WEBPACK VAR INJECTION */(function(${args}) {`;
+	}
+
+	contextArgument(module, block) {
+		if(this === block) {
+			return module.exportsArgument;
+		}
+		return "this";
+	}
+
+	/*
+	 * creates the end part of a IIFE around the module to inject a variable name
+	 * (function(...){
+	 * }.call(...))   <- this part
+	 */
+	variableInjectionFunctionWrapperEndCode(module, varExpressions, block) {
+		const firstParam = this.contextArgument(module, block);
+		const furtherParams = varExpressions.map(e => e.source()).join(", ");
+		return `}.call(${firstParam}, ${furtherParams}))`;
+	}
+
+	splitVariablesInUniqueNamedChunks(vars) {
+		const startState = [
+			[]
+		];
+		return vars.reduce((chunks, variable) => {
+			const current = chunks[chunks.length - 1];
+			// check if variable with same name exists already
+			// if so create a new chunk of variables.
+			const variableNameAlreadyExists = current.some(v => v.name === variable.name);
+
+			if(variableNameAlreadyExists) {
+				// start new chunk with current variable
+				chunks.push([variable]);
+			} else {
+				// else add it to current chunk
+				current.push(variable);
+			}
+			return chunks;
+		}, startState);
+	}
+
+}
+
+module.exports = JavascriptGenerator;
diff --git a/lib/JavascriptModulesPlugin.js b/lib/JavascriptModulesPlugin.js
index 7fb08c5e9..1f0899380 100644
--- a/lib/JavascriptModulesPlugin.js
+++ b/lib/JavascriptModulesPlugin.js
@@ -7,6 +7,7 @@
 const Parser = require("./Parser");
 const Template = require("./Template");
 const ConcatSource = require("webpack-sources").ConcatSource;
+const JavascriptGenerator = require("./JavascriptGenerator");
 
 class JavascriptModulesPlugin {
 	apply(compiler) {
@@ -22,6 +23,15 @@ class JavascriptModulesPlugin {
 			normalModuleFactory.hooks.createParser.for("javascript/esm").tap("JavascriptModulesPlugin", options => {
 				return new Parser(options, "module");
 			});
+			normalModuleFactory.hooks.createGenerator.for("javascript/auto").tap("JavascriptModulesPlugin", options => {
+				return new JavascriptGenerator(options);
+			});
+			normalModuleFactory.hooks.createGenerator.for("javascript/dynamic").tap("JavascriptModulesPlugin", options => {
+				return new JavascriptGenerator(options);
+			});
+			normalModuleFactory.hooks.createGenerator.for("javascript/esm").tap("JavascriptModulesPlugin", options => {
+				return new JavascriptGenerator(options);
+			});
 			compilation.mainTemplate.hooks.renderManifest.tap("JavascriptModulesPlugin", (result, options) => {
 				const chunk = options.chunk;
 				const hash = options.hash;
diff --git a/lib/JsonGenerator.js b/lib/JsonGenerator.js
new file mode 100644
index 000000000..b5cd17c78
--- /dev/null
+++ b/lib/JsonGenerator.js
@@ -0,0 +1,35 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const ConcatSource = require("webpack-sources").ConcatSource;
+
+const stringifySafe = data => JSON.stringify(data)
+	.replace(/\u2028|\u2029/g, str => str === "\u2029" ? "\\u2029" : "\\u2028"); // invalid in JavaScript but valid JSON
+
+class JsonGenerator {
+	generate(module, dependencyTemplates, runtimeTemplate) {
+		const source = new ConcatSource();
+		const data = module.buildInfo.jsonData;
+		if(Array.isArray(module.buildMeta.providedExports) && !module.isUsed("default")) {
+			// Only some exports are used: We can optimize here, by only generating a part of the JSON
+			const reducedJson = {};
+			for(const exportName of module.buildMeta.providedExports) {
+				if(exportName === "default")
+					continue;
+				const used = module.isUsed(exportName);
+				if(used) {
+					reducedJson[used] = data[exportName];
+				}
+			}
+			source.add(`${module.moduleArgument}.exports = ${stringifySafe(reducedJson)};`);
+		} else {
+			source.add(`${module.moduleArgument}.exports = ${stringifySafe(data)};`);
+		}
+		return source;
+	}
+}
+
+module.exports = JsonGenerator;
diff --git a/lib/JsonModulesPlugin.js b/lib/JsonModulesPlugin.js
index 8b717d9d8..c32d13943 100644
--- a/lib/JsonModulesPlugin.js
+++ b/lib/JsonModulesPlugin.js
@@ -5,10 +5,7 @@
 "use strict";
 
 const JsonParser = require("./JsonParser");
-const ConcatSource = require("webpack-sources").ConcatSource;
-
-const stringifySafe = data => JSON.stringify(data)
-	.replace(/\u2028|\u2029/g, str => str === "\u2029" ? "\\u2029" : "\\u2028"); // invalid in JavaScript but valid JSON
+const JsonGenerator = require("./JsonGenerator");
 
 class JsonModulesPlugin {
 	apply(compiler) {
@@ -18,29 +15,8 @@ class JsonModulesPlugin {
 			normalModuleFactory.hooks.createParser.for("json").tap("JsonModulesPlugin", () => {
 				return new JsonParser();
 			});
-			compilation.moduleTemplates.javascript.hooks.content.tap("JsonModulesPlugin", (moduleSource, module) => {
-				if(module.type && module.type.startsWith("json")) {
-					const source = new ConcatSource();
-					const data = module.buildInfo.jsonData;
-					if(Array.isArray(module.buildMeta.providedExports) && !module.isUsed("default")) {
-						// Only some exports are used: We can optimize here, by only generating a part of the JSON
-						const reducedJson = {};
-						for(const exportName of module.buildMeta.providedExports) {
-							if(exportName === "default")
-								continue;
-							const used = module.isUsed(exportName);
-							if(used) {
-								reducedJson[used] = data[exportName];
-							}
-						}
-						source.add(`${module.moduleArgument}.exports = ${stringifySafe(reducedJson)};`);
-					} else {
-						source.add(`${module.moduleArgument}.exports = ${stringifySafe(data)};`);
-					}
-					return source;
-				} else {
-					return moduleSource;
-				}
+			normalModuleFactory.hooks.createGenerator.for("json").tap("JsonModulesPlugin", () => {
+				return new JsonGenerator();
 			});
 		});
 	}
diff --git a/lib/NormalModule.js b/lib/NormalModule.js
index 980bfea07..5f956c958 100644
--- a/lib/NormalModule.js
+++ b/lib/NormalModule.js
@@ -10,7 +10,6 @@ const NativeModule = require("module");
 const SourceMapSource = require("webpack-sources").SourceMapSource;
 const OriginalSource = require("webpack-sources").OriginalSource;
 const RawSource = require("webpack-sources").RawSource;
-const ReplaceSource = require("webpack-sources").ReplaceSource;
 const CachedSource = require("webpack-sources").CachedSource;
 const LineToLineMappedSource = require("webpack-sources").LineToLineMappedSource;
 
@@ -65,7 +64,17 @@ const dependencyTemplatesHashMap = new WeakMap();
 
 class NormalModule extends Module {
 
-	constructor(type, request, userRequest, rawRequest, loaders, resource, parser, resolveOptions) {
+	constructor({
+		type,
+		request,
+		userRequest,
+		rawRequest,
+		loaders,
+		resource,
+		parser,
+		generator,
+		resolveOptions
+	}) {
 		super(type);
 
 		// Info from Factory
@@ -74,6 +83,7 @@ class NormalModule extends Module {
 		this.rawRequest = rawRequest;
 		this.binary = type.startsWith("webassembly");
 		this.parser = parser;
+		this.generator = generator;
 		this.resource = resource;
 		this.context = getContext(resource);
 		this.loaders = loaders;
@@ -353,180 +363,19 @@ class NormalModule extends Module {
 		return `${this.hash}-${dtHash}`;
 	}
 
-	sourceDependency(dependency, dependencyTemplates, source, runtimeTemplate) {
-		const template = dependencyTemplates.get(dependency.constructor);
-		if(!template) throw new Error("No template for dependency: " + dependency.constructor.name);
-		template.apply(dependency, source, runtimeTemplate, dependencyTemplates);
-	}
-
-	sourceVariables(variable, availableVars, dependencyTemplates, runtimeTemplate) {
-		const name = variable.name;
-		const expr = variable.expressionSource(dependencyTemplates, runtimeTemplate);
-
-		if(availableVars.some(v => v.name === name && v.expression.source() === expr.source())) {
-			return;
-		}
-		return {
-			name: name,
-			expression: expr
-		};
-	}
-
-	/*
-	 * creates the start part of a IIFE around the module to inject a variable name
-	 * (function(...){   <- this part
-	 * }.call(...))
-	 */
-	variableInjectionFunctionWrapperStartCode(varNames) {
-		const args = varNames.join(", ");
-		return `/* WEBPACK VAR INJECTION */(function(${args}) {`;
-	}
-
-	contextArgument(block) {
-		if(this === block) {
-			return this.exportsArgument;
-		}
-		return "this";
-	}
-
-	/*
-	 * creates the end part of a IIFE around the module to inject a variable name
-	 * (function(...){
-	 * }.call(...))   <- this part
-	 */
-	variableInjectionFunctionWrapperEndCode(varExpressions, block) {
-		const firstParam = this.contextArgument(block);
-		const furtherParams = varExpressions.map(e => e.source()).join(", ");
-		return `}.call(${firstParam}, ${furtherParams}))`;
-	}
-
-	splitVariablesInUniqueNamedChunks(vars) {
-		const startState = [
-			[]
-		];
-		return vars.reduce((chunks, variable) => {
-			const current = chunks[chunks.length - 1];
-			// check if variable with same name exists already
-			// if so create a new chunk of variables.
-			const variableNameAlreadyExists = current.some(v => v.name === variable.name);
-
-			if(variableNameAlreadyExists) {
-				// start new chunk with current variable
-				chunks.push([variable]);
-			} else {
-				// else add it to current chunk
-				current.push(variable);
-			}
-			return chunks;
-		}, startState);
-	}
-
-	sourceBlock(block, availableVars, dependencyTemplates, source, runtimeTemplate) {
-		for(const dependency of block.dependencies) {
-			this.sourceDependency(dependency, dependencyTemplates, source, runtimeTemplate);
-		}
-
-		/**
-		 * Get the variables of all blocks that we need to inject.
-		 * These will contain the variable name and its expression.
-		 * The name will be added as a paramter in a IIFE the expression as its value.
-		 */
-		const vars = block.variables.reduce((result, value) => {
-			const variable = this.sourceVariables(
-				value, availableVars, dependencyTemplates, runtimeTemplate);
-
-			if(variable) {
-				result.push(variable);
-			}
-
-			return result;
-		}, []);
-
-		/**
-		 * if we actually have variables
-		 * this is important as how #splitVariablesInUniqueNamedChunks works
-		 * it will always return an array in an array which would lead to a IIFE wrapper around
-		 * a module if we do this with an empty vars array.
-		 */
-		if(vars.length > 0) {
-			/**
-			 * Split all variables up into chunks of unique names.
-			 * e.g. imagine you have the following variable names that need to be injected:
-			 * [foo, bar, baz, foo, some, more]
-			 * we can not inject "foo" twice, therefore we just make two IIFEs like so:
-			 * (function(foo, bar, baz){
-			 *   (function(foo, some, more){
-			 *     ...
-			 *   }(...));
-			 * }(...));
-			 *
-			 * "splitVariablesInUniqueNamedChunks" splits the variables shown above up to this:
-			 * [[foo, bar, baz], [foo, some, more]]
-			 */
-			const injectionVariableChunks = this.splitVariablesInUniqueNamedChunks(vars);
-
-			// create all the beginnings of IIFEs
-			const functionWrapperStarts = injectionVariableChunks.map((variableChunk) => {
-				return this.variableInjectionFunctionWrapperStartCode(
-					variableChunk.map(variable => variable.name)
-				);
-			});
-
-			// and all the ends
-			const functionWrapperEnds = injectionVariableChunks.map((variableChunk) => {
-				return this.variableInjectionFunctionWrapperEndCode(
-					variableChunk.map(variable => variable.expression), block
-				);
-			});
-
-			// join them to one big string
-			const varStartCode = functionWrapperStarts.join("");
-
-			// reverse the ends first before joining them, as the last added must be the inner most
-			const varEndCode = functionWrapperEnds.reverse().join("");
-
-			// if we have anything, add it to the source
-			if(varStartCode && varEndCode) {
-				const start = block.range ? block.range[0] : -10;
-				const end = block.range ? block.range[1] : (this._source.size() + 1);
-				source.insert(start + 0.5, varStartCode);
-				source.insert(end + 0.5, "\n/* WEBPACK VAR INJECTION */" + varEndCode);
-			}
-		}
-
-		for(const childBlock of block.blocks) {
-			this.sourceBlock(
-				childBlock,
-				availableVars.concat(vars),
-				dependencyTemplates,
-				source,
-				runtimeTemplate
-			);
-		}
-	}
-
 	source(dependencyTemplates, runtimeTemplate) {
-		if(this.type.startsWith("javascript")) {
-			const hashDigest = this.getHashDigest(dependencyTemplates);
-			if(this._cachedSourceHash === hashDigest) {
-				// We can reuse the cached source
-				return this._cachedSource;
-			}
-
-			if(!this._source) {
-				return new RawSource("throw new Error('No source available');");
-			}
-
-			const source = new ReplaceSource(this._source);
+		const hashDigest = this.getHashDigest(dependencyTemplates);
+		if(this._cachedSourceHash === hashDigest) {
+			// We can reuse the cached source
+			return this._cachedSource;
+		}
 
-			this.sourceBlock(this, [], dependencyTemplates, source, runtimeTemplate);
+		const source = this.generator.generate(this, dependencyTemplates, runtimeTemplate);
 
-			const cachedSource = new CachedSource(source);
-			this._cachedSource = cachedSource;
-			this._cachedSourceHash = hashDigest;
-			return cachedSource;
-		}
-		return this._source;
+		const cachedSource = new CachedSource(source);
+		this._cachedSource = cachedSource;
+		this._cachedSourceHash = hashDigest;
+		return cachedSource;
 	}
 
 	originalSource() {
diff --git a/lib/NormalModuleFactory.js b/lib/NormalModuleFactory.js
index c184ef30d..74fb3b6e5 100644
--- a/lib/NormalModuleFactory.js
+++ b/lib/NormalModuleFactory.js
@@ -61,6 +61,8 @@ class NormalModuleFactory extends Tapable {
 			module: new SyncWaterfallHook(["module", "data"]),
 			createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
 			parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
+			createGenerator: new HookMap(() => new SyncBailHook(["generatorOptions"])),
+			generator: new HookMap(() => new SyncHook(["generator", "generatorOptions"])),
 		};
 		this._pluginCompat.tap("NormalModuleFactory", options => {
 			switch(options.name) {
@@ -89,6 +91,7 @@ class NormalModuleFactory extends Tapable {
 		this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache);
 		this.context = context || "";
 		this.parserCache = Object.create(null);
+		this.generatorCache = Object.create(null);
 		this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
 			let resolver = this.hooks.resolver.call(null);
 
@@ -118,16 +121,7 @@ class NormalModuleFactory extends Tapable {
 							return callback(new Error("Empty dependency (no request)"));
 						}
 
-						createdModule = new NormalModule(
-							result.type,
-							result.request,
-							result.userRequest,
-							result.rawRequest,
-							result.loaders,
-							result.resource,
-							result.parser,
-							result.resolveOptions
-						);
+						createdModule = new NormalModule(result);
 					}
 
 					createdModule = this.hooks.module.call(createdModule, result);
@@ -254,6 +248,7 @@ class NormalModuleFactory extends Tapable {
 							settings,
 							type,
 							parser: this.getParser(type, settings.parser),
+							generator: this.getGenerator(type, settings.generator),
 							resolveOptions
 						});
 					});
@@ -349,6 +344,29 @@ class NormalModuleFactory extends Tapable {
 		return parser;
 	}
 
+	getGenerator(type, generatorOptions) {
+		let ident = type;
+		if(generatorOptions) {
+			if(generatorOptions.ident)
+				ident = `${type}|${generatorOptions.ident}`;
+			else
+				ident = JSON.stringify([type, generatorOptions]);
+		}
+		if(ident in this.generatorCache) {
+			return this.generatorCache[ident];
+		}
+		return this.generatorCache[ident] = this.createGenerator(type, generatorOptions);
+	}
+
+	createGenerator(type, generatorOptions = {}) {
+		const generator = this.hooks.createGenerator.for(type).call(generatorOptions);
+		if(!generator) {
+			throw new Error(`No generator registered for ${type}`);
+		}
+		this.hooks.generator.for(type).call(generator, generatorOptions);
+		return generator;
+	}
+
 	getResolver(type, resolveOptions) {
 		return this.resolverFactory.get(type, resolveOptions || EMPTY_RESOLVE_OPTIONS);
 	}
diff --git a/lib/WebAssemblyGenerator.js b/lib/WebAssemblyGenerator.js
new file mode 100644
index 000000000..76d718146
--- /dev/null
+++ b/lib/WebAssemblyGenerator.js
@@ -0,0 +1,13 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+class WebAssemblyGenerator {
+	generate(module) {
+		return module.originalSource();
+	}
+}
+
+module.exports = WebAssemblyGenerator;
diff --git a/lib/WebAssemblyModulesPlugin.js b/lib/WebAssemblyModulesPlugin.js
index 8cbc76149..303087b91 100644
--- a/lib/WebAssemblyModulesPlugin.js
+++ b/lib/WebAssemblyModulesPlugin.js
@@ -5,6 +5,7 @@
 "use strict";
 
 const WebAssemblyParser = require("./WebAssemblyParser");
+const WebAssemblyGenerator = require("./WebAssemblyGenerator");
 const WebAssemblyImportDependency = require("./dependencies/WebAssemblyImportDependency");
 
 class WebAssemblyModulesPlugin {
@@ -18,6 +19,10 @@ class WebAssemblyModulesPlugin {
 				return new WebAssemblyParser();
 			});
 
+			normalModuleFactory.hooks.createGenerator.for("webassembly/experimental").tap("WebAssemblyModulesPlugin", () => {
+				return new WebAssemblyGenerator();
+			});
+
 			compilation.chunkTemplate.hooks.renderManifest.tap("WebAssemblyModulesPlugin", (result, options) => {
 				const chunk = options.chunk;
 				const outputOptions = options.outputOptions;


Test patch:
diff --git a/test/NormalModule.unittest.js b/test/NormalModule.unittest.js
index 7cf7351e9..3b272b4f5 100644
--- a/test/NormalModule.unittest.js
+++ b/test/NormalModule.unittest.js
@@ -25,15 +25,17 @@ describe("NormalModule", function() {
 		parser = {
 			parse() {}
 		};
-		normalModule = new NormalModule(
-			"javascript/auto",
+		normalModule = new NormalModule({
+			type: "javascript/auto",
 			request,
 			userRequest,
 			rawRequest,
 			loaders,
 			resource,
-			parser
-		);
+			parser,
+			generator: null,
+			resolveOptions: {}
+		});
 		normalModule.buildInfo = {
 			cacheable: true
 		};
@@ -70,15 +72,15 @@ describe("NormalModule", function() {
 		describe("given a userRequest containing loaders", function() {
 			beforeEach(function() {
 				userRequest = "some/userRequest!some/other/userRequest!some/thing/is/off/here";
-				normalModule = new NormalModule(
-					"javascript/auto",
+				normalModule = new NormalModule({
+					type: "javascript/auto",
 					request,
 					userRequest,
 					rawRequest,
 					loaders,
 					resource,
 					parser
-				);
+				});
 			});
 			it("contextifies every path in the userRequest", function() {
 				normalModule.libIdent({
@@ -89,15 +91,15 @@ describe("NormalModule", function() {
 		describe("given a userRequest containing query parameters", function() {
 			it("ignores paths in query parameters", function() {
 				userRequest = "some/context/loader?query=foo\\bar&otherPath=testpath/other";
-				normalModule = new NormalModule(
-					"javascript/auto",
+				normalModule = new NormalModule({
+					type: "javascript/auto",
 					request,
 					userRequest,
 					rawRequest,
 					loaders,
 					resource,
 					parser
-				);
+				});
 				normalModule.libIdent({
 					context: "some/context",
 				}).should.eql("./loader?query=foo\\bar&otherPath=testpath/other");
@@ -113,15 +115,15 @@ describe("NormalModule", function() {
 			const baseResource = "some/resource";
 			beforeEach(function() {
 				resource = baseResource + "?some=query";
-				normalModule = new NormalModule(
-					"javascript/auto",
+				normalModule = new NormalModule({
+					type: "javascript/auto",
 					request,
 					userRequest,
 					rawRequest,
 					loaders,
 					resource,
 					parser
-				);
+				});
 			});
 			it("return only the part before the ?-sign", function() {
 				normalModule.nameForCondition().should.eql(baseResource);
@@ -158,18 +160,6 @@ describe("NormalModule", function() {
 		});
 	});
 
-	describe("#source", function() {
-		describe("without the module having any source", function() {
-			beforeEach(function() {
-				normalModule._source = null;
-			});
-			it("returns a Source containing an Error", function() {
-				normalModule.source().should.be.instanceOf(RawSource);
-				normalModule.source().source().should.eql("throw new Error('No source available');");
-			});
-		});
-	});
-
 	describe("#originalSource", function() {
 		let expectedSource = "some source";
 		beforeEach(function() {
@@ -290,44 +280,6 @@ describe("NormalModule", function() {
 			});
 		});
 	});
-	describe("#splitVariablesInUniqueNamedChunks", function() {
-		let variables;
-		beforeEach(function() {
-			variables = [{
-				name: "foo"
-			}, {
-				name: "bar"
-			}, {
-				name: "baz"
-			}, {
-				name: "some"
-			}, {
-				name: "more"
-			}];
-		});
-		describe("given an empty array of vars", function() {
-			it("returns an empty array", function() {
-				normalModule.splitVariablesInUniqueNamedChunks([]).should.eql([
-					[]
-				]);
-			});
-		});
-		describe("given an array of distrinct variables", function() {
-			it("returns an array containing an array containing the variables", function() {
-				normalModule.splitVariablesInUniqueNamedChunks(variables).should.eql([variables]);
-			});
-		});
-		describe("given an array with duplicate variables", function() {
-			it("returns several arrays each containing only distinct variable names", function() {
-				normalModule.splitVariablesInUniqueNamedChunks(variables.concat(variables)).should.eql([variables, variables]);
-			});
-			describe("and a duplicate as the last variable", function() {
-				it("returns correctly split distinct arrays", function() {
-					normalModule.splitVariablesInUniqueNamedChunks(variables.concat(variables).concat(variables[0])).should.eql([variables, variables, [variables[0]]]);
-				});
-			});
-		});
-	});
 
 	describe("#applyNoParseRule", function() {
 		let rule;