123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 |
- /*
- 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) {
- const splitPath = r.split("?");
- splitPath[0] = path.relative(context, splitPath[0]);
- if(path.sep === "\\")
- splitPath[0] = splitPath[0].replace(/\\/g, "/");
- if(splitPath[0].indexOf("../") !== 0)
- splitPath[0] = "./" + splitPath[0];
- return splitPath.join("?");
- }).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);
- }
- }
-
- const dependencyTemplatesHashMap = new WeakMap();
-
- 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;
- }
-
- if(typeof rule === "function") {
- return rule(content);
- }
- // 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 = Date.now();
- 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(dependencyTemplates) {
- let dtHash = dependencyTemplatesHashMap.get("hash");
- const hash = crypto.createHash("md5");
- this.updateHash(hash);
- hash.update(`${dtHash}`);
- 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.reduce((result, value) => {
- const variable = this.sourceVariables(
- value, availableVars, dependencyTemplates, outputOptions, requestShortener);
-
- 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);
- }
- }
-
- block.blocks.forEach((block) =>
- this.sourceBlock(
- block,
- availableVars.concat(vars),
- dependencyTemplates,
- source,
- outputOptions,
- requestShortener
- )
- );
- }
-
- source(dependencyTemplates, outputOptions, requestShortener) {
- const hashDigest = this.getHashDigest(dependencyTemplates);
- 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;
|