TemplatedPathPlugin.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Jason Anderson @diurnalist
  4. */
  5. "use strict";
  6. const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi,
  7. REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/gi,
  8. REGEXP_MODULEHASH = /\[modulehash(?::(\d+))?\]/gi,
  9. REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/gi,
  10. REGEXP_NAME = /\[name\]/gi,
  11. REGEXP_ID = /\[id\]/gi,
  12. REGEXP_MODULEID = /\[moduleid\]/gi,
  13. REGEXP_FILE = /\[file\]/gi,
  14. REGEXP_QUERY = /\[query\]/gi,
  15. REGEXP_FILEBASE = /\[filebase\]/gi;
  16. // Using global RegExp for .test is dangerous
  17. // We use a normal RegExp instead of .test
  18. const REGEXP_HASH_FOR_TEST = new RegExp(REGEXP_HASH.source, "i"),
  19. REGEXP_CHUNKHASH_FOR_TEST = new RegExp(REGEXP_CHUNKHASH.source, "i"),
  20. REGEXP_CONTENTHASH_FOR_TEST = new RegExp(REGEXP_CONTENTHASH.source, "i"),
  21. REGEXP_NAME_FOR_TEST = new RegExp(REGEXP_NAME.source, "i");
  22. const withHashLength = (replacer, handlerFn) => {
  23. const fn = (match, hashLength, ...args) => {
  24. const length = hashLength && parseInt(hashLength, 10);
  25. if (length && handlerFn) {
  26. return handlerFn(length);
  27. }
  28. const hash = replacer(match, hashLength, ...args);
  29. return length ? hash.slice(0, length) : hash;
  30. };
  31. return fn;
  32. };
  33. const getReplacer = (value, allowEmpty) => {
  34. const fn = (match, ...args) => {
  35. // last argument in replacer is the entire input string
  36. const input = args[args.length - 1];
  37. if (value === null || value === undefined) {
  38. if (!allowEmpty) {
  39. throw new Error(
  40. `Path variable ${match} not implemented in this context: ${input}`
  41. );
  42. }
  43. return "";
  44. } else {
  45. return `${value}`;
  46. }
  47. };
  48. return fn;
  49. };
  50. const replacePathVariables = (path, data) => {
  51. const chunk = data.chunk;
  52. const chunkId = chunk && chunk.id;
  53. const chunkName = chunk && (chunk.name || chunk.id);
  54. const chunkHash = chunk && (chunk.renderedHash || chunk.hash);
  55. const chunkHashWithLength = chunk && chunk.hashWithLength;
  56. const contentHashType = data.contentHashType;
  57. const contentHash =
  58. (chunk && chunk.contentHash && chunk.contentHash[contentHashType]) ||
  59. data.contentHash;
  60. const contentHashWithLength =
  61. (chunk &&
  62. chunk.contentHashWithLength &&
  63. chunk.contentHashWithLength[contentHashType]) ||
  64. data.contentHashWithLength;
  65. const module = data.module;
  66. const moduleId = module && module.id;
  67. const moduleHash = module && (module.renderedHash || module.hash);
  68. const moduleHashWithLength = module && module.hashWithLength;
  69. if (typeof path === "function") {
  70. path = path(data);
  71. }
  72. if (
  73. data.noChunkHash &&
  74. (REGEXP_CHUNKHASH_FOR_TEST.test(path) ||
  75. REGEXP_CONTENTHASH_FOR_TEST.test(path))
  76. ) {
  77. throw new Error(
  78. `Cannot use [chunkhash] or [contenthash] for chunk in '${path}' (use [hash] instead)`
  79. );
  80. }
  81. return (
  82. path
  83. .replace(
  84. REGEXP_HASH,
  85. withHashLength(getReplacer(data.hash), data.hashWithLength)
  86. )
  87. .replace(
  88. REGEXP_CHUNKHASH,
  89. withHashLength(getReplacer(chunkHash), chunkHashWithLength)
  90. )
  91. .replace(
  92. REGEXP_CONTENTHASH,
  93. withHashLength(getReplacer(contentHash), contentHashWithLength)
  94. )
  95. .replace(
  96. REGEXP_MODULEHASH,
  97. withHashLength(getReplacer(moduleHash), moduleHashWithLength)
  98. )
  99. .replace(REGEXP_ID, getReplacer(chunkId))
  100. .replace(REGEXP_MODULEID, getReplacer(moduleId))
  101. .replace(REGEXP_NAME, getReplacer(chunkName))
  102. .replace(REGEXP_FILE, getReplacer(data.filename))
  103. .replace(REGEXP_FILEBASE, getReplacer(data.basename))
  104. // query is optional, it's OK if it's in a path but there's nothing to replace it with
  105. .replace(REGEXP_QUERY, getReplacer(data.query, true))
  106. );
  107. };
  108. class TemplatedPathPlugin {
  109. apply(compiler) {
  110. compiler.hooks.compilation.tap("TemplatedPathPlugin", compilation => {
  111. const mainTemplate = compilation.mainTemplate;
  112. mainTemplate.hooks.assetPath.tap(
  113. "TemplatedPathPlugin",
  114. replacePathVariables
  115. );
  116. mainTemplate.hooks.globalHash.tap(
  117. "TemplatedPathPlugin",
  118. (chunk, paths) => {
  119. const outputOptions = mainTemplate.outputOptions;
  120. const publicPath = outputOptions.publicPath || "";
  121. const filename = outputOptions.filename || "";
  122. const chunkFilename =
  123. outputOptions.chunkFilename || outputOptions.filename;
  124. if (
  125. REGEXP_HASH_FOR_TEST.test(publicPath) ||
  126. REGEXP_CHUNKHASH_FOR_TEST.test(publicPath) ||
  127. REGEXP_CONTENTHASH_FOR_TEST.test(publicPath) ||
  128. REGEXP_NAME_FOR_TEST.test(publicPath)
  129. )
  130. return true;
  131. if (REGEXP_HASH_FOR_TEST.test(filename)) return true;
  132. if (REGEXP_HASH_FOR_TEST.test(chunkFilename)) return true;
  133. if (REGEXP_HASH_FOR_TEST.test(paths.join("|"))) return true;
  134. }
  135. );
  136. mainTemplate.hooks.hashForChunk.tap(
  137. "TemplatedPathPlugin",
  138. (hash, chunk) => {
  139. const outputOptions = mainTemplate.outputOptions;
  140. const chunkFilename =
  141. outputOptions.chunkFilename || outputOptions.filename;
  142. if (REGEXP_CHUNKHASH_FOR_TEST.test(chunkFilename)) {
  143. hash.update(JSON.stringify(chunk.getChunkMaps(true).hash));
  144. }
  145. if (REGEXP_CONTENTHASH_FOR_TEST.test(chunkFilename)) {
  146. hash.update(
  147. JSON.stringify(
  148. chunk.getChunkMaps(true).contentHash.javascript || {}
  149. )
  150. );
  151. }
  152. if (REGEXP_NAME_FOR_TEST.test(chunkFilename)) {
  153. hash.update(JSON.stringify(chunk.getChunkMaps(true).name));
  154. }
  155. }
  156. );
  157. });
  158. }
  159. }
  160. module.exports = TemplatedPathPlugin;