NormalModule.js 16 KB

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