NormalModule.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const path = require("path");
  7. const NativeModule = require("module");
  8. const crypto = require("crypto");
  9. const SourceMapSource = require("webpack-sources").SourceMapSource;
  10. const OriginalSource = require("webpack-sources").OriginalSource;
  11. const RawSource = require("webpack-sources").RawSource;
  12. const ReplaceSource = require("webpack-sources").ReplaceSource;
  13. const CachedSource = require("webpack-sources").CachedSource;
  14. const LineToLineMappedSource = require("webpack-sources").LineToLineMappedSource;
  15. const WebpackError = require("./WebpackError");
  16. const Module = require("./Module");
  17. const ModuleParseError = require("./ModuleParseError");
  18. const ModuleBuildError = require("./ModuleBuildError");
  19. const ModuleError = require("./ModuleError");
  20. const ModuleWarning = require("./ModuleWarning");
  21. const runLoaders = require("loader-runner").runLoaders;
  22. const getContext = require("loader-runner").getContext;
  23. function asString(buf) {
  24. if(Buffer.isBuffer(buf)) {
  25. return buf.toString("utf-8");
  26. }
  27. return buf;
  28. }
  29. function contextify(context, request) {
  30. return request.split("!").map(function(r) {
  31. let rp = path.relative(context, r);
  32. if(path.sep === "\\")
  33. rp = rp.replace(/\\/g, "/");
  34. if(rp.indexOf("../") !== 0)
  35. rp = "./" + rp;
  36. return rp;
  37. }).join("!");
  38. }
  39. class NonErrorEmittedError extends WebpackError {
  40. constructor(error) {
  41. super();
  42. this.name = "NonErrorEmittedError";
  43. this.message = "(Emitted value instead of an instance of Error) " + error;
  44. Error.captureStackTrace(this, this.constructor);
  45. }
  46. }
  47. class NormalModule extends Module {
  48. constructor(request, userRequest, rawRequest, loaders, resource, parser) {
  49. super();
  50. this.request = request;
  51. this.userRequest = userRequest;
  52. this.rawRequest = rawRequest;
  53. this.parser = parser;
  54. this.resource = resource;
  55. this.context = getContext(resource);
  56. this.loaders = loaders;
  57. this.fileDependencies = [];
  58. this.contextDependencies = [];
  59. this.warnings = [];
  60. this.errors = [];
  61. this.error = null;
  62. this._source = null;
  63. this.assets = {};
  64. this.built = false;
  65. this._cachedSource = null;
  66. }
  67. identifier() {
  68. return this.request;
  69. }
  70. readableIdentifier(requestShortener) {
  71. return requestShortener.shorten(this.userRequest);
  72. }
  73. libIdent(options) {
  74. return contextify(options.context, this.userRequest);
  75. }
  76. nameForCondition() {
  77. const idx = this.resource.indexOf("?");
  78. if(idx >= 0) return this.resource.substr(0, idx);
  79. return this.resource;
  80. }
  81. createSourceForAsset(name, content, sourceMap) {
  82. if(!sourceMap) {
  83. return new RawSource(content);
  84. }
  85. if(typeof sourceMap === "string") {
  86. return new OriginalSource(content, sourceMap);
  87. }
  88. return new SourceMapSource(content, name, sourceMap);
  89. }
  90. createLoaderContext(resolver, options, compilation, fs) {
  91. const loaderContext = {
  92. version: 2,
  93. emitWarning: (warning) => {
  94. if(!(warning instanceof Error))
  95. warning = new NonErrorEmittedError(warning);
  96. this.warnings.push(new ModuleWarning(this, warning));
  97. },
  98. emitError: (error) => {
  99. if(!(error instanceof Error))
  100. error = new NonErrorEmittedError(error);
  101. this.errors.push(new ModuleError(this, error));
  102. },
  103. exec: (code, filename) => {
  104. const module = new NativeModule(filename, this);
  105. module.paths = NativeModule._nodeModulePaths(this.context);
  106. module.filename = filename;
  107. module._compile(code, filename);
  108. return module.exports;
  109. },
  110. resolve(context, request, callback) {
  111. resolver.resolve({}, context, request, callback);
  112. },
  113. resolveSync(context, request) {
  114. return resolver.resolveSync({}, context, request);
  115. },
  116. emitFile: (name, content, sourceMap) => {
  117. this.assets[name] = this.createSourceForAsset(name, content, sourceMap);
  118. },
  119. options: options,
  120. webpack: true,
  121. sourceMap: !!this.useSourceMap,
  122. _module: this,
  123. _compilation: compilation,
  124. _compiler: compilation.compiler,
  125. fs: fs,
  126. };
  127. compilation.applyPlugins("normal-module-loader", loaderContext, this);
  128. if(options.loader)
  129. Object.assign(loaderContext, options.loader);
  130. return loaderContext;
  131. }
  132. createSource(source, resourceBuffer, sourceMap) {
  133. // if there is no identifier return raw source
  134. if(!this.identifier) {
  135. return new RawSource(source);
  136. }
  137. // from here on we assume we have an identifier
  138. const identifier = this.identifier();
  139. if(this.lineToLine && resourceBuffer) {
  140. return new LineToLineMappedSource(
  141. source, identifier, asString(resourceBuffer));
  142. }
  143. if(this.useSourceMap && sourceMap) {
  144. return new SourceMapSource(source, identifier, sourceMap);
  145. }
  146. return new OriginalSource(source, identifier);
  147. }
  148. doBuild(options, compilation, resolver, fs, callback) {
  149. this.cacheable = false;
  150. const loaderContext = this.createLoaderContext(resolver, options, compilation, fs);
  151. runLoaders({
  152. resource: this.resource,
  153. loaders: this.loaders,
  154. context: loaderContext,
  155. readResource: fs.readFile.bind(fs)
  156. }, (err, result) => {
  157. if(result) {
  158. this.cacheable = result.cacheable;
  159. this.fileDependencies = result.fileDependencies;
  160. this.contextDependencies = result.contextDependencies;
  161. }
  162. if(err) {
  163. const error = new ModuleBuildError(this, err);
  164. return callback(error);
  165. }
  166. const resourceBuffer = result.resourceBuffer;
  167. const source = result.result[0];
  168. const sourceMap = result.result[1];
  169. if(!Buffer.isBuffer(source) && typeof source !== "string") {
  170. const error = new ModuleBuildError(this, new Error("Final loader didn't return a Buffer or String"));
  171. return callback(error);
  172. }
  173. this._source = this.createSource(asString(source), resourceBuffer, sourceMap);
  174. return callback();
  175. });
  176. }
  177. disconnect() {
  178. this.built = false;
  179. super.disconnect();
  180. }
  181. markModuleAsErrored(error) {
  182. this.meta = null;
  183. this.error = error;
  184. this.errors.push(this.error);
  185. this._source = new RawSource("throw new Error(" + JSON.stringify(this.error.message) + ");");
  186. }
  187. applyNoParseRule(rule, content) {
  188. // must start with "rule" if rule is a string
  189. if(typeof rule === "string") {
  190. return content.indexOf(rule) === 0;
  191. }
  192. // we assume rule is a regexp
  193. return rule.test(content);
  194. }
  195. // check if module should not be parsed
  196. // returns "true" if the module should !not! be parsed
  197. // returns "false" if the module !must! be parsed
  198. shouldPreventParsing(noParseRule, request) {
  199. // if no noParseRule exists, return false
  200. // the module !must! be parsed.
  201. if(!noParseRule) {
  202. return false;
  203. }
  204. // we only have one rule to check
  205. if(!Array.isArray(noParseRule)) {
  206. // returns "true" if the module is !not! to be parsed
  207. return this.applyNoParseRule(noParseRule, request);
  208. }
  209. for(let i = 0; i < noParseRule.length; i++) {
  210. const rule = noParseRule[i];
  211. // early exit on first truthy match
  212. // this module is !not! to be parsed
  213. if(this.applyNoParseRule(rule, request)) {
  214. return true;
  215. }
  216. }
  217. // no match found, so this module !should! be parsed
  218. return false;
  219. }
  220. build(options, compilation, resolver, fs, callback) {
  221. this.buildTimestamp = Date.now();
  222. this.built = true;
  223. this._source = null;
  224. this.error = null;
  225. this.errors.length = 0;
  226. this.warnings.length = 0;
  227. this.meta = {};
  228. return this.doBuild(options, compilation, resolver, fs, (err) => {
  229. this.dependencies.length = 0;
  230. this.variables.length = 0;
  231. this.blocks.length = 0;
  232. this._cachedSource = null;
  233. // if we have an error mark module as failed and exit
  234. if(err) {
  235. this.markModuleAsErrored(err);
  236. return callback();
  237. }
  238. // check if this module should !not! be parsed.
  239. // if so, exit here;
  240. const noParseRule = options.module && options.module.noParse;
  241. if(this.shouldPreventParsing(noParseRule, this.request)) {
  242. return callback();
  243. }
  244. try {
  245. this.parser.parse(this._source.source(), {
  246. current: this,
  247. module: this,
  248. compilation: compilation,
  249. options: options
  250. });
  251. } catch(e) {
  252. const source = this._source.source();
  253. const error = new ModuleParseError(this, source, e);
  254. this.markModuleAsErrored(error);
  255. return callback();
  256. }
  257. return callback();
  258. });
  259. }
  260. getHashDigest() {
  261. const hash = crypto.createHash("md5");
  262. this.updateHash(hash);
  263. return hash.digest("hex");
  264. }
  265. sourceDependency(dependency, dependencyTemplates, source, outputOptions, requestShortener) {
  266. const template = dependencyTemplates.get(dependency.constructor);
  267. if(!template) throw new Error("No template for dependency: " + dependency.constructor.name);
  268. template.apply(dependency, source, outputOptions, requestShortener, dependencyTemplates);
  269. }
  270. sourceVariables(variable, availableVars, dependencyTemplates, outputOptions, requestShortener) {
  271. const name = variable.name;
  272. const expr = variable.expressionSource(dependencyTemplates, outputOptions, requestShortener);
  273. if(availableVars.some(v => v.name === name && v.expression.source() === expr.source())) {
  274. return;
  275. }
  276. return {
  277. name: name,
  278. expression: expr
  279. };
  280. }
  281. /*
  282. * creates the start part of a IIFE around the module to inject a variable name
  283. * (function(...){ <- this part
  284. * }.call(...))
  285. */
  286. variableInjectionFunctionWrapperStartCode(varNames) {
  287. const args = varNames.join(", ");
  288. return `/* WEBPACK VAR INJECTION */(function(${args}) {`;
  289. }
  290. contextArgument(block) {
  291. if(this === block) {
  292. return this.exportsArgument || "exports";
  293. }
  294. return "this";
  295. }
  296. /*
  297. * creates the end part of a IIFE around the module to inject a variable name
  298. * (function(...){
  299. * }.call(...)) <- this part
  300. */
  301. variableInjectionFunctionWrapperEndCode(varExpressions, block) {
  302. const firstParam = this.contextArgument(block);
  303. const furtherParams = varExpressions.map(e => e.source()).join(", ");
  304. return `}.call(${firstParam}, ${furtherParams}))`;
  305. }
  306. splitVariablesInUniqueNamedChunks(vars) {
  307. const startState = [
  308. []
  309. ];
  310. return vars.reduce((chunks, variable) => {
  311. const current = chunks[chunks.length - 1];
  312. // check if variable with same name exists already
  313. // if so create a new chunk of variables.
  314. const variableNameAlreadyExists = current.some(v => v.name === variable.name);
  315. if(variableNameAlreadyExists) {
  316. // start new chunk with current variable
  317. chunks.push([variable]);
  318. } else {
  319. // else add it to current chunk
  320. current.push(variable);
  321. }
  322. return chunks;
  323. }, startState);
  324. }
  325. sourceBlock(block, availableVars, dependencyTemplates, source, outputOptions, requestShortener) {
  326. block.dependencies.forEach((dependency) => this.sourceDependency(
  327. dependency, dependencyTemplates, source, outputOptions, requestShortener));
  328. /**
  329. * Get the variables of all blocks that we need to inject.
  330. * These will contain the variable name and its expression.
  331. * The name will be added as a paramter in a IIFE the expression as its value.
  332. */
  333. const vars = block.variables.map((variable) => this.sourceVariables(
  334. variable, availableVars, dependencyTemplates, outputOptions, requestShortener))
  335. .filter(Boolean);
  336. /**
  337. * if we actually have variables
  338. * this is important as how #splitVariablesInUniqueNamedChunks works
  339. * it will always return an array in an array which would lead to a IIFE wrapper around
  340. * a module if we do this with an empty vars array.
  341. */
  342. if(vars.length > 0) {
  343. /**
  344. * Split all variables up into chunks of unique names.
  345. * e.g. imagine you have the following variable names that need to be injected:
  346. * [foo, bar, baz, foo, some, more]
  347. * we can not inject "foo" twice, therefore we just make two IIFEs like so:
  348. * (function(foo, bar, baz){
  349. * (function(foo, some, more){
  350. * ...
  351. * }(...));
  352. * }(...));
  353. *
  354. * "splitVariablesInUniqueNamedChunks" splits the variables shown above up to this:
  355. * [[foo, bar, baz], [foo, some, more]]
  356. */
  357. const injectionVariableChunks = this.splitVariablesInUniqueNamedChunks(vars);
  358. // create all the beginnings of IIFEs
  359. const functionWrapperStarts = injectionVariableChunks.map((variableChunk) => variableChunk.map(variable => variable.name))
  360. .map(names => this.variableInjectionFunctionWrapperStartCode(names));
  361. // and all the ends
  362. const functionWrapperEnds = injectionVariableChunks.map((variableChunk) => variableChunk.map(variable => variable.expression))
  363. .map(expressions => this.variableInjectionFunctionWrapperEndCode(expressions, block));
  364. // join them to one big string
  365. const varStartCode = functionWrapperStarts.join("");
  366. // reverse the ends first before joining them, as the last added must be the inner most
  367. const varEndCode = functionWrapperEnds.reverse().join("");
  368. // if we have anything, add it to the source
  369. if(varStartCode && varEndCode) {
  370. const start = block.range ? block.range[0] : -10;
  371. const end = block.range ? block.range[1] : (this._source.size() + 1);
  372. source.insert(start + 0.5, varStartCode);
  373. source.insert(end + 0.5, "\n/* WEBPACK VAR INJECTION */" + varEndCode);
  374. }
  375. }
  376. block.blocks.forEach((block) => this.sourceBlock(
  377. block, availableVars.concat(vars), dependencyTemplates, source, outputOptions, requestShortener));
  378. }
  379. source(dependencyTemplates, outputOptions, requestShortener) {
  380. const hashDigest = this.getHashDigest();
  381. if(this._cachedSource && this._cachedSource.hash === hashDigest) {
  382. return this._cachedSource.source;
  383. }
  384. if(!this._source) {
  385. return new RawSource("throw new Error('No source available');");
  386. }
  387. const source = new ReplaceSource(this._source);
  388. this._cachedSource = {
  389. source: source,
  390. hash: hashDigest
  391. };
  392. this.sourceBlock(this, [], dependencyTemplates, source, outputOptions, requestShortener);
  393. return new CachedSource(source);
  394. }
  395. originalSource() {
  396. return this._source;
  397. }
  398. getHighestTimestamp(keys, timestampsByKey) {
  399. let highestTimestamp = 0;
  400. for(let i = 0; i < keys.length; i++) {
  401. const key = keys[i];
  402. const timestamp = timestampsByKey[key];
  403. // if there is no timestamp yet, early return with Infinity
  404. if(!timestamp) return Infinity;
  405. highestTimestamp = Math.max(highestTimestamp, timestamp);
  406. }
  407. return highestTimestamp;
  408. }
  409. needRebuild(fileTimestamps, contextTimestamps) {
  410. const highestFileDepTimestamp = this.getHighestTimestamp(
  411. this.fileDependencies, fileTimestamps);
  412. // if the hightest is Infinity, we need a rebuild
  413. // exit early here.
  414. if(highestFileDepTimestamp === Infinity) {
  415. return true;
  416. }
  417. const highestContextDepTimestamp = this.getHighestTimestamp(
  418. this.contextDependencies, contextTimestamps);
  419. // Again if the hightest is Infinity, we need a rebuild
  420. // exit early here.
  421. if(highestContextDepTimestamp === Infinity) {
  422. return true;
  423. }
  424. // else take the highest of file and context timestamps and compare
  425. // to last build timestamp
  426. return Math.max(highestContextDepTimestamp, highestFileDepTimestamp) >= this.buildTimestamp;
  427. }
  428. size() {
  429. return this._source ? this._source.size() : -1;
  430. }
  431. updateHashWithSource(hash) {
  432. if(!this._source) {
  433. hash.update("null");
  434. return;
  435. }
  436. hash.update("source");
  437. this._source.updateHash(hash);
  438. }
  439. updateHashWithMeta(hash) {
  440. hash.update("meta");
  441. hash.update(JSON.stringify(this.meta));
  442. }
  443. updateHash(hash) {
  444. this.updateHashWithSource(hash);
  445. this.updateHashWithMeta(hash);
  446. super.updateHash(hash);
  447. }
  448. }
  449. module.exports = NormalModule;