mapped-list.js 4.1 KB

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