aboutsummaryrefslogtreecommitdiff
path: root/node_modules/webpack/lib/NormalModule.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/webpack/lib/NormalModule.js')
-rw-r--r--node_modules/webpack/lib/NormalModule.js524
1 files changed, 524 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/NormalModule.js b/node_modules/webpack/lib/NormalModule.js
new file mode 100644
index 000000000..949bfef87
--- /dev/null
+++ b/node_modules/webpack/lib/NormalModule.js
@@ -0,0 +1,524 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const path = require("path");
+const NativeModule = require("module");
+const crypto = require("crypto");
+
+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;
+
+const WebpackError = require("./WebpackError");
+const Module = require("./Module");
+const ModuleParseError = require("./ModuleParseError");
+const ModuleBuildError = require("./ModuleBuildError");
+const ModuleError = require("./ModuleError");
+const ModuleWarning = require("./ModuleWarning");
+
+const runLoaders = require("loader-runner").runLoaders;
+const getContext = require("loader-runner").getContext;
+
+function asString(buf) {
+ if(Buffer.isBuffer(buf)) {
+ return buf.toString("utf-8");
+ }
+ return buf;
+}
+
+function contextify(context, request) {
+ return request.split("!").map(function(r) {
+ let rp = path.relative(context, r);
+ if(path.sep === "\\")
+ rp = rp.replace(/\\/g, "/");
+ if(rp.indexOf("../") !== 0)
+ rp = "./" + rp;
+ return rp;
+ }).join("!");
+}
+
+class NonErrorEmittedError extends WebpackError {
+ constructor(error) {
+ super();
+
+ this.name = "NonErrorEmittedError";
+ this.message = "(Emitted value instead of an instance of Error) " + error;
+
+ Error.captureStackTrace(this, this.constructor);
+ }
+}
+
+class NormalModule extends Module {
+
+ constructor(request, userRequest, rawRequest, loaders, resource, parser) {
+ super();
+ this.request = request;
+ this.userRequest = userRequest;
+ this.rawRequest = rawRequest;
+ this.parser = parser;
+ this.resource = resource;
+ this.context = getContext(resource);
+ this.loaders = loaders;
+ this.fileDependencies = [];
+ this.contextDependencies = [];
+ this.warnings = [];
+ this.errors = [];
+ this.error = null;
+ this._source = null;
+ this.assets = {};
+ this.built = false;
+ this._cachedSource = null;
+ }
+
+ identifier() {
+ return this.request;
+ }
+
+ readableIdentifier(requestShortener) {
+ return requestShortener.shorten(this.userRequest);
+ }
+
+ libIdent(options) {
+ return contextify(options.context, this.userRequest);
+ }
+
+ nameForCondition() {
+ const idx = this.resource.indexOf("?");
+ if(idx >= 0) return this.resource.substr(0, idx);
+ return this.resource;
+ }
+
+ createSourceForAsset(name, content, sourceMap) {
+ if(!sourceMap) {
+ return new RawSource(content);
+ }
+
+ if(typeof sourceMap === "string") {
+ return new OriginalSource(content, sourceMap);
+ }
+
+ return new SourceMapSource(content, name, sourceMap);
+ }
+
+ createLoaderContext(resolver, options, compilation, fs) {
+ const loaderContext = {
+ version: 2,
+ emitWarning: (warning) => {
+ if(!(warning instanceof Error))
+ warning = new NonErrorEmittedError(warning);
+ this.warnings.push(new ModuleWarning(this, warning));
+ },
+ emitError: (error) => {
+ if(!(error instanceof Error))
+ error = new NonErrorEmittedError(error);
+ this.errors.push(new ModuleError(this, error));
+ },
+ exec: (code, filename) => {
+ const module = new NativeModule(filename, this);
+ module.paths = NativeModule._nodeModulePaths(this.context);
+ module.filename = filename;
+ module._compile(code, filename);
+ return module.exports;
+ },
+ resolve(context, request, callback) {
+ resolver.resolve({}, context, request, callback);
+ },
+ resolveSync(context, request) {
+ return resolver.resolveSync({}, context, request);
+ },
+ emitFile: (name, content, sourceMap) => {
+ this.assets[name] = this.createSourceForAsset(name, content, sourceMap);
+ },
+ options: options,
+ webpack: true,
+ sourceMap: !!this.useSourceMap,
+ _module: this,
+ _compilation: compilation,
+ _compiler: compilation.compiler,
+ fs: fs,
+ };
+
+ compilation.applyPlugins("normal-module-loader", loaderContext, this);
+ if(options.loader)
+ Object.assign(loaderContext, options.loader);
+
+ return loaderContext;
+ }
+
+ createSource(source, resourceBuffer, sourceMap) {
+ // if there is no identifier return raw source
+ if(!this.identifier) {
+ return new RawSource(source);
+ }
+
+ // from here on we assume we have an identifier
+ const identifier = this.identifier();
+
+ if(this.lineToLine && resourceBuffer) {
+ return new LineToLineMappedSource(
+ source, identifier, asString(resourceBuffer));
+ }
+
+ if(this.useSourceMap && sourceMap) {
+ return new SourceMapSource(source, identifier, sourceMap);
+ }
+
+ return new OriginalSource(source, identifier);
+ }
+
+ doBuild(options, compilation, resolver, fs, callback) {
+ this.cacheable = false;
+ const loaderContext = this.createLoaderContext(resolver, options, compilation, fs);
+
+ runLoaders({
+ resource: this.resource,
+ loaders: this.loaders,
+ context: loaderContext,
+ readResource: fs.readFile.bind(fs)
+ }, (err, result) => {
+ if(result) {
+ this.cacheable = result.cacheable;
+ this.fileDependencies = result.fileDependencies;
+ this.contextDependencies = result.contextDependencies;
+ }
+
+ if(err) {
+ const error = new ModuleBuildError(this, err);
+ return callback(error);
+ }
+
+ const resourceBuffer = result.resourceBuffer;
+ const source = result.result[0];
+ const sourceMap = result.result[1];
+
+ if(!Buffer.isBuffer(source) && typeof source !== "string") {
+ const error = new ModuleBuildError(this, new Error("Final loader didn't return a Buffer or String"));
+ return callback(error);
+ }
+
+ this._source = this.createSource(asString(source), resourceBuffer, sourceMap);
+ return callback();
+ });
+ }
+
+ disconnect() {
+ this.built = false;
+ super.disconnect();
+ }
+
+ markModuleAsErrored(error) {
+ this.meta = null;
+ this.error = error;
+ this.errors.push(this.error);
+ this._source = new RawSource("throw new Error(" + JSON.stringify(this.error.message) + ");");
+ }
+
+ applyNoParseRule(rule, content) {
+ // must start with "rule" if rule is a string
+ if(typeof rule === "string") {
+ return content.indexOf(rule) === 0;
+ }
+ // we assume rule is a regexp
+ return rule.test(content);
+ }
+
+ // check if module should not be parsed
+ // returns "true" if the module should !not! be parsed
+ // returns "false" if the module !must! be parsed
+ shouldPreventParsing(noParseRule, request) {
+ // if no noParseRule exists, return false
+ // the module !must! be parsed.
+ if(!noParseRule) {
+ return false;
+ }
+
+ // we only have one rule to check
+ if(!Array.isArray(noParseRule)) {
+ // returns "true" if the module is !not! to be parsed
+ return this.applyNoParseRule(noParseRule, request);
+ }
+
+ for(let i = 0; i < noParseRule.length; i++) {
+ const rule = noParseRule[i];
+ // early exit on first truthy match
+ // this module is !not! to be parsed
+ if(this.applyNoParseRule(rule, request)) {
+ return true;
+ }
+ }
+ // no match found, so this module !should! be parsed
+ return false;
+ }
+
+ build(options, compilation, resolver, fs, callback) {
+ this.buildTimestamp = new Date().getTime();
+ this.built = true;
+ this._source = null;
+ this.error = null;
+ this.errors.length = 0;
+ this.warnings.length = 0;
+ this.meta = {};
+
+ return this.doBuild(options, compilation, resolver, fs, (err) => {
+ this.dependencies.length = 0;
+ this.variables.length = 0;
+ this.blocks.length = 0;
+ this._cachedSource = null;
+
+ // if we have an error mark module as failed and exit
+ if(err) {
+ this.markModuleAsErrored(err);
+ return callback();
+ }
+
+ // check if this module should !not! be parsed.
+ // if so, exit here;
+ const noParseRule = options.module && options.module.noParse;
+ if(this.shouldPreventParsing(noParseRule, this.request)) {
+ return callback();
+ }
+
+ try {
+ this.parser.parse(this._source.source(), {
+ current: this,
+ module: this,
+ compilation: compilation,
+ options: options
+ });
+ } catch(e) {
+ const source = this._source.source();
+ const error = new ModuleParseError(this, source, e);
+ this.markModuleAsErrored(error);
+ return callback();
+ }
+ return callback();
+ });
+ }
+
+ getHashDigest() {
+ const hash = crypto.createHash("md5");
+ this.updateHash(hash);
+ return hash.digest("hex");
+ }
+
+ sourceDependency(dependency, dependencyTemplates, source, outputOptions, requestShortener) {
+ const template = dependencyTemplates.get(dependency.constructor);
+ if(!template) throw new Error("No template for dependency: " + dependency.constructor.name);
+ template.apply(dependency, source, outputOptions, requestShortener, dependencyTemplates);
+ }
+
+ sourceVariables(variable, availableVars, dependencyTemplates, outputOptions, requestShortener) {
+ const name = variable.name;
+ const expr = variable.expressionSource(dependencyTemplates, outputOptions, requestShortener);
+
+ 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 || "exports";
+ }
+ 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, outputOptions, requestShortener) {
+ block.dependencies.forEach((dependency) => this.sourceDependency(
+ dependency, dependencyTemplates, source, outputOptions, requestShortener));
+
+ /**
+ * 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.map((variable) => this.sourceVariables(
+ variable, availableVars, dependencyTemplates, outputOptions, requestShortener))
+ .filter(Boolean);
+
+ /**
+ * 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) => variableChunk.map(variable => variable.name))
+ .map(names => this.variableInjectionFunctionWrapperStartCode(names));
+
+ // and all the ends
+ const functionWrapperEnds = injectionVariableChunks.map((variableChunk) => variableChunk.map(variable => variable.expression))
+ .map(expressions => this.variableInjectionFunctionWrapperEndCode(expressions, 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);
+ }
+ }
+ block.blocks.forEach((block) => this.sourceBlock(
+ block, availableVars.concat(vars), dependencyTemplates, source, outputOptions, requestShortener));
+ }
+
+ source(dependencyTemplates, outputOptions, requestShortener) {
+ const hashDigest = this.getHashDigest();
+ if(this._cachedSource && this._cachedSource.hash === hashDigest) {
+ return this._cachedSource.source;
+ }
+
+ if(!this._source) {
+ return new RawSource("throw new Error('No source available');");
+ }
+
+ const source = new ReplaceSource(this._source);
+ this._cachedSource = {
+ source: source,
+ hash: hashDigest
+ };
+
+ this.sourceBlock(this, [], dependencyTemplates, source, outputOptions, requestShortener);
+ return new CachedSource(source);
+ }
+
+ originalSource() {
+ return this._source;
+ }
+
+ getHighestTimestamp(keys, timestampsByKey) {
+ let highestTimestamp = 0;
+ for(let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ const timestamp = timestampsByKey[key];
+ // if there is no timestamp yet, early return with Infinity
+ if(!timestamp) return Infinity;
+ highestTimestamp = Math.max(highestTimestamp, timestamp);
+ }
+ return highestTimestamp;
+ }
+
+ needRebuild(fileTimestamps, contextTimestamps) {
+ const highestFileDepTimestamp = this.getHighestTimestamp(
+ this.fileDependencies, fileTimestamps);
+ // if the hightest is Infinity, we need a rebuild
+ // exit early here.
+ if(highestFileDepTimestamp === Infinity) {
+ return true;
+ }
+
+ const highestContextDepTimestamp = this.getHighestTimestamp(
+ this.contextDependencies, contextTimestamps);
+
+ // Again if the hightest is Infinity, we need a rebuild
+ // exit early here.
+ if(highestContextDepTimestamp === Infinity) {
+ return true;
+ }
+
+ // else take the highest of file and context timestamps and compare
+ // to last build timestamp
+ return Math.max(highestContextDepTimestamp, highestFileDepTimestamp) >= this.buildTimestamp;
+ }
+
+ size() {
+ return this._source ? this._source.size() : -1;
+ }
+
+ updateHashWithSource(hash) {
+ if(!this._source) {
+ hash.update("null");
+ return;
+ }
+ hash.update("source");
+ this._source.updateHash(hash);
+ }
+
+ updateHashWithMeta(hash) {
+ hash.update("meta");
+ hash.update(JSON.stringify(this.meta));
+ }
+
+ updateHash(hash) {
+ this.updateHashWithSource(hash);
+ this.updateHashWithMeta(hash);
+ super.updateHash(hash);
+ }
+
+}
+
+module.exports = NormalModule;