-
Notifications
You must be signed in to change notification settings - Fork 273
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(spritehash): add ability to use
[spritehash]
substitution toke…
…n in spriteFilename
- Loading branch information
Showing
3 changed files
with
183 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
const { getHashDigest } = require('loader-utils'); | ||
|
||
/** | ||
* Partially stolen from loader-utils#interpolateName | ||
* @param {string} input | ||
* @param {Object<string, string>} tokensToHash | ||
* @return {string} | ||
* @see https://github.com/webpack/loader-utils#interpolatename | ||
* @example | ||
* hashTokens('[chunkname]-[spritehash:6]', { spritehash: '<svg>...</svg>' }) | ||
* // => '[chunkname]-8e04fd' | ||
*/ | ||
module.exports = function hashTokens(input, tokensToHash) { | ||
let result = input; | ||
|
||
Object.keys(tokensToHash).forEach((key) => { | ||
const content = tokensToHash[key]; | ||
// eslint-disable-next-line no-useless-escape | ||
const re = new RegExp(`\\[(?:(\\w+):)?${key}(?::([a-z]+\\d*))?(?::(\\d+))?]`, 'ig'); | ||
|
||
result = result.replace(re, (all, hashType, digestType, maxLength) => { | ||
return getHashDigest(content, hashType, digestType, parseInt(maxLength, 10)); | ||
}); | ||
}); | ||
|
||
return result; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
const loaderDefaults = require('../config').loader; | ||
const getAllModules = require('./get-all-modules'); | ||
const isModuleShouldBeExtracted = require('./is-module-should-be-extracted'); | ||
const getLoaderOptions = require('./get-loader-options'); | ||
const getLoadersRules = require('./get-loaders-rules'); | ||
const getMatchedRule = require('./get-matched-rule'); | ||
const getModuleChunk = require('./get-module-chunk'); | ||
const hashTokens = require('./hash-tokens'); | ||
|
||
const spriteLoaderPath = require.resolve('../loader'); | ||
|
||
class MappedListItem { | ||
/** | ||
* @param {SpriteSymbol} symbol | ||
* @param {NormalModule} module | ||
* @param {string} spriteFilename | ||
*/ | ||
constructor(symbol, module, spriteFilename) { | ||
this.symbol = symbol; | ||
this.module = module; | ||
this.spriteFilename = spriteFilename; | ||
this.resource = symbol.request.file; | ||
} | ||
|
||
get url() { | ||
const { spriteFilename, symbol } = this; | ||
return `${spriteFilename}#${symbol.id}`; | ||
} | ||
|
||
get useUrl() { | ||
const { spriteFilename, symbol } = this; | ||
return `${spriteFilename}#${symbol.useId}`; | ||
} | ||
} | ||
|
||
class MappedList { | ||
/** | ||
* @param {SpriteSymbol[]} symbols | ||
* @param {Compilation} compilation | ||
*/ | ||
constructor(symbols, compilation) { | ||
const { compiler } = compilation; | ||
|
||
this.symbols = symbols; | ||
this.rules = getLoadersRules(compiler); | ||
this.allModules = getAllModules(compilation); | ||
this.spriteModules = this.allModules.filter(isModuleShouldBeExtracted); | ||
|
||
this.items = this.create(); | ||
} | ||
|
||
/** | ||
* @param {MappedListItem[]} data | ||
* @return {Object<string, MappedListItem>} | ||
*/ | ||
static groupSymbolsBySprite(data) { | ||
return data | ||
.map(item => item.spriteFilename) | ||
.filter((value, index, self) => self.indexOf(value) === index) | ||
.reduce((acc, spriteFilename) => { | ||
acc[spriteFilename] = data.filter(item => item.spriteFilename === spriteFilename); | ||
return acc; | ||
}, {}); | ||
} | ||
|
||
/** | ||
* @param {MappedListItem[]} data | ||
* @param {Function} [mapper] Custom grouper function | ||
* @return {Object<string, MappedListItem>} | ||
*/ | ||
static groupSpritesBySymbol(data, mapper) { | ||
return data.reduce((acc, item) => { | ||
if (mapper) { | ||
mapper(acc, item); | ||
} else { | ||
acc[item.resource] = item; | ||
} | ||
return acc; | ||
}, {}); | ||
} | ||
|
||
/** | ||
* @return {MappedListItem[]} | ||
*/ | ||
create() { | ||
const { symbols, spriteModules, allModules, rules } = this; | ||
const data = symbols.reduce((acc, symbol) => { | ||
const resource = symbol.request.file; | ||
const module = spriteModules.find(m => m.resource === resource); | ||
const rule = getMatchedRule(resource, rules); | ||
const options = rule ? getLoaderOptions(spriteLoaderPath, rule) : null; | ||
let spriteFilename = (options && options.spriteFilename) | ||
? options.spriteFilename | ||
: loaderDefaults.spriteFilename; | ||
|
||
const chunk = module ? getModuleChunk(module, allModules) : null; | ||
if (chunk) { | ||
spriteFilename = spriteFilename.replace('[chunkname]', chunk.name); | ||
} | ||
|
||
if (rule && module) { | ||
acc.push(new MappedListItem(symbol, module, spriteFilename)); | ||
} | ||
|
||
return acc; | ||
}, []); | ||
|
||
// Additional pass to interpolate hash in spriteFilename | ||
const itemsBySpriteFilename = MappedList.groupSymbolsBySprite(data); | ||
const filenames = Object.keys(itemsBySpriteFilename); | ||
|
||
filenames.forEach((filename) => { | ||
const items = itemsBySpriteFilename[filename]; | ||
const spriteSymbols = items.map(item => item.symbol); | ||
const content = spriteSymbols.map(s => s.render()).join(''); | ||
const interpolatedName = hashTokens(filename, { spritehash: content }); | ||
|
||
items | ||
.filter(item => item.spriteFilename !== interpolatedName) | ||
.forEach(item => item.spriteFilename = interpolatedName); | ||
}); | ||
|
||
return data; | ||
} | ||
|
||
/** | ||
* @return {Object<string, MappedListItem>} | ||
*/ | ||
groupSymbolsBySprite() { | ||
return MappedList.groupSymbolsBySprite(this.items); | ||
} | ||
|
||
/** | ||
* @param {Function} [mapper] Custom grouper function | ||
* @return {Object<string, MappedListItem>} | ||
*/ | ||
groupSpritesBySymbol(mapper) { | ||
return MappedList.groupSpritesBySymbol(this.items, mapper); | ||
} | ||
} | ||
|
||
module.exports = MappedList; |