mapped-list.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. // TODO refactor this smelly code!
  2. const loaderDefaults = require('../config').loader;
  3. const getAllModules = require('./get-all-modules');
  4. const isModuleShouldBeExtracted = require('./is-module-should-be-extracted');
  5. const getLoaderOptions = require('./get-loader-options');
  6. const getLoadersRules = require('./get-loaders-rules');
  7. const getMatchedRule = require('./get-matched-rule');
  8. const getModuleChunk = require('./get-module-chunk');
  9. const interpolate = require('./interpolate');
  10. const spriteLoaderPath = require.resolve('../loader');
  11. class MappedListItem {
  12. /**
  13. * @param {SpriteSymbol} symbol
  14. * @param {NormalModule} module
  15. * @param {string} spriteFilename
  16. */
  17. constructor(symbol, module, spriteFilename) {
  18. this.symbol = symbol;
  19. this.module = module;
  20. this.resource = symbol.request.file;
  21. this.spriteFilename = spriteFilename;
  22. }
  23. get url() {
  24. return `${this.spriteFilename}#${this.symbol.id}`;
  25. }
  26. get useUrl() {
  27. return `${this.spriteFilename}#${this.symbol.useId}`;
  28. }
  29. }
  30. class MappedList {
  31. /**
  32. * @param {SpriteSymbol[]} symbols
  33. * @param {Compilation} compilation
  34. */
  35. constructor(symbols, compilation, shouldLog = false) {
  36. const { compiler } = compilation;
  37. this.symbols = symbols;
  38. this.rules = getLoadersRules(compiler);
  39. this.allModules = getAllModules(compilation);
  40. this.spriteModules = this.allModules.filter(isModuleShouldBeExtracted);
  41. this.shouldLog = shouldLog;
  42. this.items = this.create();
  43. }
  44. /**
  45. * @param {MappedListItem[]} data
  46. * @return {Object<string, MappedListItem>}
  47. */
  48. static groupItemsBySpriteFilename(data) {
  49. return data
  50. .map(item => item.spriteFilename)
  51. .filter((value, index, self) => self.indexOf(value) === index)
  52. .reduce((acc, spriteFilename) => {
  53. acc[spriteFilename] = data.filter(item => item.spriteFilename === spriteFilename);
  54. return acc;
  55. }, {});
  56. }
  57. /**
  58. * @param {MappedListItem[]} data
  59. * @param {Function} [mapper] Custom grouper function
  60. * @return {Object<string, MappedListItem>}
  61. */
  62. static groupItemsBySymbolFile(data, mapper) {
  63. return data.reduce((acc, item) => {
  64. if (mapper) {
  65. mapper(acc, item);
  66. } else {
  67. acc[item.resource] = item;
  68. }
  69. return acc;
  70. }, {});
  71. }
  72. /**
  73. * @return {MappedListItem[]}
  74. */
  75. create() {
  76. const { symbols, spriteModules, allModules, rules } = this;
  77. const data = symbols.reduce((acc, symbol) => {
  78. const resource = symbol.request.file;
  79. const module = spriteModules.find((m) => {
  80. return 'resource' in m ? m.resource.split('?')[0] === resource : false;
  81. });
  82. const rule = getMatchedRule(resource, rules);
  83. const options = rule ? getLoaderOptions(spriteLoaderPath, rule) : null;
  84. let spriteFilename = (options && options.spriteFilename)
  85. ? options.spriteFilename
  86. : loaderDefaults.spriteFilename;
  87. const chunk = module ? getModuleChunk(module, allModules) : null;
  88. if (typeof spriteFilename !== 'function' && chunk && chunk.name) {
  89. spriteFilename = spriteFilename.replace('[chunkname]', chunk.name);
  90. } else if (typeof spriteFilename === 'function') {
  91. spriteFilename = spriteFilename(resource);
  92. }
  93. if (rule && module) {
  94. acc.push(new MappedListItem(symbol, module, spriteFilename));
  95. }
  96. return acc;
  97. }, []);
  98. // Additional pass to interpolate [hash] in spriteFilename
  99. const itemsBySpriteFilename = MappedList.groupItemsBySpriteFilename(data);
  100. const filenames = Object.keys(itemsBySpriteFilename);
  101. filenames.forEach((filename) => {
  102. if (!filename.includes('hash')) {
  103. return;
  104. }
  105. const items = itemsBySpriteFilename[filename];
  106. const spriteSymbols = items.map(item => item.symbol);
  107. const content = spriteSymbols.map(s => s.render()).join('');
  108. const interpolatedName = interpolate(filename, {
  109. resourcePath: filename,
  110. content
  111. });
  112. items
  113. .filter(item => item.spriteFilename !== interpolatedName)
  114. .forEach(item => item.spriteFilename = interpolatedName);
  115. });
  116. return data;
  117. }
  118. /**
  119. * @return {Object<string, MappedListItem>}
  120. */
  121. groupItemsBySpriteFilename() {
  122. return MappedList.groupItemsBySpriteFilename(this.items);
  123. }
  124. /**
  125. * @param {Function} [mapper] Custom grouper function
  126. * @return {Object<string, MappedListItem>}
  127. */
  128. groupItemsBySymbolFile(mapper) {
  129. return MappedList.groupItemsBySymbolFile(this.items, mapper);
  130. }
  131. }
  132. module.exports = MappedList;