NormalModule.js 16KB

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