progress on migrating to heex templates and font-icons

This commit is contained in:
Adam Piontek 2022-08-13 07:32:36 -04:00
commit 3eff955672
21793 changed files with 2161968 additions and 16895 deletions

View file

@ -0,0 +1,94 @@
/* eslint-disable max-len */
const fs = require('fs');
const PACKAGE_NAME = require('../package.json').name;
module.exports = {
PACKAGE_NAME,
NAMESPACE: fs.realpathSync(__dirname),
EXTRACTABLE_MODULE_ISSUER_PATTERN: /\.(css|sass|scss|less|styl|html)$/i,
SPRITE_PLACEHOLDER_PATTERN: /\{\{sprite-filename\|([^}}]*)\}\};?/gi,
/**
* Overridable loader options
* @typedef {Object} SVGSpriteLoaderConfig
*/
loader: {
/**
* How `<symbol id>` should be named.
* Full list of supported patterns see at [loader-utils#interpolatename docs](https://github.com/webpack/loader-utils#interpolatename).
* @type {string}
*/
symbolId: '[name]',
/**
* Regular expression passed to interpolateName.
* Supports the interpolateName [N] pattern inserting the N-th match.
* @type {string}
*/
symbolRegExp: '',
/**
* Path to Node.js module which generates client runtime.
* @type {string}
*/
runtimeGenerator: require.resolve('./runtime-generator'),
/**
* Arbitrary data passed to runtime generator.
* @type {*}
*/
runtimeOptions: undefined,
/**
* Should runtime be compatible with earlier v0.* loader versions.
* Will be removed in 3 version.
* @type {boolean}
* @deprecated
*/
runtimeCompat: false,
/**
* Path to sprite module which will be compiled and executed at runtime.
* By default depends on 'target' webpack config option:
* - `svg-sprite-loader/runtime/browser-sprite.build` for 'web' target.
* - `svg-sprite-loader/runtime/sprite.build` for all other targets.
* @type {string}
* @autoconfigured
*/
spriteModule: require.resolve('../runtime/browser-sprite.build'),
/**
* Path to symbol module.
* By default depends on 'target' webpack config option:
* - `svg-baker-runtime/browser-symbol` for 'web' target.
* - `svg-baker-runtime/symbol` for all other targets.
* @type {string}
* @autoconfigured
*/
symbolModule: require.resolve('svg-baker-runtime/browser-symbol'),
/**
* Generated export format:
* - when `true` loader will produce `export default ...`.
* - when `false` the result is `module.exports = ...`.
* By default depends on used webpack version. `true` for Webpack >= 2, `false` otherwise.
* @type {boolean}
* @autoconfigured
*/
esModule: true,
/**
* Turns loader in extract mode.
* Enables automatically if SVG image was imported from css/scss/sass/less/styl/html.
* @type {boolean}
* @autoconfigured
*/
extract: false,
/**
* Filename for generated sprite. `[chunkname]` placeholder can be used.
* @type {string}
*/
spriteFilename: 'sprite.svg'
}
};

View file

@ -0,0 +1,47 @@
const merge = require('deepmerge');
const defaults = require('./config');
const utils = require('./utils');
const loaderDefaults = defaults.loader;
const isomorphicSpriteModule = require.resolve('../runtime/sprite.build');
const isomorphicSymbolModule = require.resolve('svg-baker-runtime/symbol');
const isTargetBrowser = target => target === 'web' || target === 'electron-renderer'
|| target === 'node-webkit' || target === 'nwjs';
/**
* @param {Object} params
* @param {Object} [params.config] Parsed loader config {@see SVGSpriteLoaderConfig}
* @param {LoaderContext} context Loader context {@see https://webpack.js.org/api/loaders/#the-loader-context}
* @return {Object}
*/
module.exports = function configurator({ config, context, target }) {
const module = context._module;
const compiler = context._compiler;
const compilerName = compiler.name;
const autoConfigured = {
spriteModule: isTargetBrowser(target) ? loaderDefaults.spriteModule : isomorphicSpriteModule,
symbolModule: isTargetBrowser(target) ? loaderDefaults.symbolModule : isomorphicSymbolModule,
extract: utils.isModuleShouldBeExtracted(module),
esModule: context.version && context.version >= 2
};
const finalConfig = merge.all([loaderDefaults, autoConfigured, config || {}]);
/**
* esModule should be `false` when compiles via extract-text-webpack-plugin or html-webpack-plugin.
* Because this compilers executes module as usual node module so export should be always in commonjs style.
* This could be dropped when Node.js will support ES modules natively :)
* @see https://git.io/vS7Sn
* @see https://git.io/v9w60
*/
if (compilerName && (
compilerName.includes('extract-text-webpack-plugin') ||
compilerName.includes('html-webpack-plugin')
)) {
finalConfig.esModule = false;
}
return finalConfig;
};

View file

@ -0,0 +1,42 @@
const { PACKAGE_NAME } = require('./config');
class LoaderException extends Error {
constructor(message = '') {
super(`${PACKAGE_NAME} exception. ${message}`);
this.name = this.constructor.name;
/* istanbul ignore else */
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = (new Error(message)).stack;
}
}
}
class InvalidSvg extends LoaderException {
constructor(content) {
super(`\n\n${content}`);
}
}
class ExtractPluginMissingException extends LoaderException {
constructor() {
super(`${PACKAGE_NAME} in extract mode requires the corresponding plugin`);
}
}
class InvalidRuntimeException extends LoaderException {}
class RemainingLoadersInExtractModeException extends LoaderException {
constructor() {
super(`Some loaders will be applied after ${PACKAGE_NAME} in extract mode`);
}
}
exports.LoaderException = LoaderException;
exports.InvalidSvg = InvalidSvg;
exports.ExtractPluginMissingException = ExtractPluginMissingException;
exports.InvalidRuntimeException = InvalidRuntimeException;
exports.RemainingLoadersInExtractModeException = RemainingLoadersInExtractModeException;

View file

@ -0,0 +1,82 @@
const { interpolateName, getOptions } = require('loader-utils');
const urlSlug = require('url-slug');
const SVGCompiler = require('svg-baker');
const { NAMESPACE } = require('./config');
const configure = require('./configurator');
const Exceptions = require('./exceptions');
let svgCompiler = new SVGCompiler();
// eslint-disable-next-line consistent-return
module.exports = function loader(content) {
if (this.cacheable) {
this.cacheable();
}
const done = this.async();
const loaderContext = this;
const { resourcePath, loaderIndex } = loaderContext;
// webpack 1 compat
const resourceQuery = loaderContext.resourceQuery || '';
const compiler = loaderContext._compiler;
const isChildCompiler = compiler.isChild();
const parentCompiler = isChildCompiler ? compiler.parentCompilation.compiler : null;
const matchedRules = getOptions(loaderContext);
if (!content.includes('<svg')) {
throw new Exceptions.InvalidSvg(content, matchedRules);
}
const configObj = { context: loaderContext };
configObj.config = matchedRules;
configObj.target = loaderContext.target;
/**
* @type {SVGSpriteLoaderConfig}
*/
const config = configure(configObj);
if (config.extract) {
const plugin = parentCompiler
? parentCompiler.options.plugins.find(p => p.NAMESPACE && p.NAMESPACE === NAMESPACE)
: this[NAMESPACE];
if (typeof plugin === 'undefined') {
throw new Exceptions.ExtractPluginMissingException();
}
if (loaderIndex > 0) {
this.emitWarning(new Exceptions.RemainingLoadersInExtractModeException());
}
svgCompiler = plugin.svgCompiler;
}
let runtimeGenerator;
try {
runtimeGenerator = require(config.runtimeGenerator); // eslint-disable-line import/no-dynamic-require,global-require
} catch (e) {
throw new Exceptions.InvalidRuntimeException(e.message);
}
let id;
if (typeof config.symbolId === 'function') {
id = config.symbolId(resourcePath, resourceQuery);
} else {
const idPattern = config.symbolId + (resourceQuery ? `--${urlSlug(resourceQuery)}` : '');
id = interpolateName(loaderContext, idPattern, {
content,
context: compiler.context,
regExp: config.symbolRegExp
});
}
svgCompiler.addSymbol({ id, content, path: resourcePath + resourceQuery })
.then((symbol) => {
const runtime = runtimeGenerator({ symbol, config, context: loaderContext.context, loaderContext });
done(null, runtime);
}).catch(done);
};
module.exports.NAMESPACE = NAMESPACE;

223
assets_old/node_modules/svg-sprite-loader/lib/plugin.js generated vendored Normal file
View file

@ -0,0 +1,223 @@
/* eslint-disable import/no-extraneous-dependencies */
const merge = require('deepmerge');
const Promise = require('bluebird');
const SVGCompiler = require('svg-baker');
const spriteFactory = require('svg-baker/lib/sprite-factory');
const Sprite = require('svg-baker/lib/sprite');
const { NAMESPACE } = require('./config');
const {
MappedList,
replaceInModuleSource,
replaceSpritePlaceholder,
getMatchedRule
} = require('./utils');
const defaultConfig = {
plainSprite: false,
spriteAttrs: {}
};
class SVGSpritePlugin {
constructor(cfg = {}) {
const config = merge.all([defaultConfig, cfg]);
this.config = config;
const spriteFactoryOptions = {
attrs: config.spriteAttrs
};
if (config.plainSprite) {
spriteFactoryOptions.styles = false;
spriteFactoryOptions.usages = false;
}
this.factory = ({ symbols }) => {
const opts = merge.all([spriteFactoryOptions, { symbols }]);
return spriteFactory(opts);
};
this.svgCompiler = new SVGCompiler();
this.rules = {};
}
/**
* This need to find plugin from loader context
*/
// eslint-disable-next-line class-methods-use-this
get NAMESPACE() {
return NAMESPACE;
}
getReplacements() {
const isPlainSprite = this.config.plainSprite === true;
const replacements = this.map.groupItemsBySymbolFile((acc, item) => {
acc[item.resource] = isPlainSprite ? item.url : item.useUrl;
});
return replacements;
}
// TODO optimize MappedList instantiation in each hook
apply(compiler) {
this.rules = getMatchedRule(compiler);
const path = this.rules.outputPath ? this.rules.outputPath : this.rules.publicPath;
this.filenamePrefix = path
? path.replace(/^\//, '')
: '';
if (compiler.hooks) {
compiler.hooks
.thisCompilation
.tap(NAMESPACE, (compilation) => {
compilation.hooks
.normalModuleLoader
.tap(NAMESPACE, loaderContext => loaderContext[NAMESPACE] = this);
compilation.hooks
.afterOptimizeChunks
.tap(NAMESPACE, () => this.afterOptimizeChunks(compilation));
if (compilation.hooks.optimizeExtractedChunks) {
compilation.hooks
.optimizeExtractedChunks
.tap(NAMESPACE, chunks => this.optimizeExtractedChunks(chunks));
}
compilation.hooks
.additionalAssets
.tapPromise(NAMESPACE, () => {
return this.additionalAssets(compilation);
});
});
compiler.hooks
.compilation
.tap(NAMESPACE, (compilation) => {
if (compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) {
compilation.hooks
.htmlWebpackPluginBeforeHtmlGeneration
.tapAsync(NAMESPACE, (htmlPluginData, callback) => {
htmlPluginData.assets.sprites = this.beforeHtmlGeneration(compilation);
callback(null, htmlPluginData);
});
}
if (compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing) {
compilation.hooks
.htmlWebpackPluginBeforeHtmlProcessing
.tapAsync(NAMESPACE, (htmlPluginData, callback) => {
htmlPluginData.html = this.beforeHtmlProcessing(htmlPluginData);
callback(null, htmlPluginData);
});
}
});
} else {
// Handle only main compilation
compiler.plugin('this-compilation', (compilation) => {
// Share svgCompiler with loader
compilation.plugin('normal-module-loader', (loaderContext) => {
loaderContext[NAMESPACE] = this;
});
// Replace placeholders with real URL to symbol (in modules processed by svg-sprite-loader)
compilation.plugin('after-optimize-chunks', () => this.afterOptimizeChunks(compilation));
// Hook into extract-text-webpack-plugin to replace placeholders with real URL to symbol
compilation.plugin('optimize-extracted-chunks', chunks => this.optimizeExtractedChunks(chunks));
// Hook into html-webpack-plugin to add `sprites` variable into template context
compilation.plugin('html-webpack-plugin-before-html-generation', (htmlPluginData, done) => {
htmlPluginData.assets.sprites = this.beforeHtmlGeneration(compilation);
done(null, htmlPluginData);
});
// Hook into html-webpack-plugin to replace placeholders with real URL to symbol
compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, done) => {
htmlPluginData.html = this.beforeHtmlProcessing(htmlPluginData);
done(null, htmlPluginData);
});
// Create sprite chunk
compilation.plugin('additional-assets', (done) => {
return this.additionalAssets(compilation)
.then(() => {
done();
return true;
})
.catch(e => done(e));
});
});
}
}
additionalAssets(compilation) {
const itemsBySprite = this.map.groupItemsBySpriteFilename();
const filenames = Object.keys(itemsBySprite);
return Promise.map(filenames, (filename) => {
const spriteSymbols = itemsBySprite[filename].map(item => item.symbol);
return Sprite.create({
symbols: spriteSymbols,
factory: this.factory
})
.then((sprite) => {
const content = sprite.render();
compilation.assets[`${this.filenamePrefix}${filename}`] = {
source() { return content; },
size() { return content.length; },
updateHash(bulkUpdateDecorator) { bulkUpdateDecorator.update(content); }
};
});
});
}
afterOptimizeChunks(compilation) {
const { symbols } = this.svgCompiler;
this.map = new MappedList(symbols, compilation);
const replacements = this.getReplacements();
this.map.items.forEach(item => replaceInModuleSource(item.module, replacements));
}
optimizeExtractedChunks(chunks) {
const replacements = this.getReplacements();
chunks.forEach((chunk) => {
let modules;
if (chunk.modulesIterable) {
modules = Array.from(chunk.modulesIterable);
} else {
modules = chunk.modules;
}
modules
// dirty hack to identify modules extracted by extract-text-webpack-plugin
// TODO refactor
.filter(module => '_originalModule' in module)
.forEach(module => replaceInModuleSource(module, replacements));
});
}
beforeHtmlGeneration(compilation) {
const itemsBySprite = this.map.groupItemsBySpriteFilename();
const sprites = Object.keys(itemsBySprite).reduce((acc, filename) => {
acc[this.filenamePrefix + filename] = compilation.assets[this.filenamePrefix + filename].source();
return acc;
}, {});
return sprites;
}
beforeHtmlProcessing(htmlPluginData) {
const replacements = this.getReplacements();
return replaceSpritePlaceholder(htmlPluginData.html, replacements);
}
}
module.exports = SVGSpritePlugin;

View file

@ -0,0 +1,57 @@
const { isAbsolute, join } = require('path');
const { stringifyRequest } = require('loader-utils');
const {
stringify,
stringifySymbol,
generateImport,
generateExport,
generateSpritePlaceholder
} = require('./utils');
/**
* @param {Object} params
* @param {SpriteSymbol} params.symbol - Sprite symbol instance {@see https://git.io/v9k8g}
* @param {SVGSpriteLoaderConfig} params.config - Parsed loader config
* @param {string} params.context - Context folder of current processing module
* @param {Object} params.loaderContext {@see https://webpack.js.org/api/loaders/#the-loader-context}
* @return {string}
*/
function runtimeGenerator(params) {
const { symbol, config, context, loaderContext } = params;
const { extract, esModule, spriteModule, symbolModule, runtimeCompat, publicPath } = config;
let runtime;
if (extract) {
const spritePlaceholder = generateSpritePlaceholder(symbol.request.file);
const path = stringify(publicPath) || '__webpack_public_path__';
const data = `{
id: ${stringify(symbol.useId)},
viewBox: ${stringify(symbol.viewBox)},
url: ${path} + ${stringify(spritePlaceholder)},
toString: function () {
return this.url;
}
}`;
runtime = generateExport(data, esModule);
} else {
const spriteModuleAbsPath = isAbsolute(spriteModule) ? spriteModule : join(context, spriteModule);
const symbolModuleAbsPath = isAbsolute(symbolModule) ? symbolModule : join(context, symbolModule);
const spriteModuleImport = stringifyRequest(loaderContext, spriteModuleAbsPath);
const symbolModuleImport = stringifyRequest(loaderContext, symbolModuleAbsPath);
runtime = [
generateImport('SpriteSymbol', symbolModuleImport, esModule),
generateImport('sprite', spriteModuleImport, esModule),
`var symbol = new SpriteSymbol(${stringifySymbol(symbol)})`,
'var result = sprite.add(symbol)',
generateExport(runtimeCompat ? '"#" + symbol.id' : 'symbol', esModule)
].join(';\n');
}
return runtime;
}
module.exports = runtimeGenerator;

View file

@ -0,0 +1,14 @@
const loaderDefaults = require('../config').loader;
/**
* @param {string} content
* @param {boolean} [esModule=false]
* @return {string}
*/
function generateExport(content, esModule = loaderDefaults.esModule) {
return esModule ?
`export default ${content}` :
`module.exports = ${content}`;
}
module.exports = generateExport;

View file

@ -0,0 +1,16 @@
const loaderDefaults = require('../config').loader;
const stringify = require('./stringify');
/**
* @param {string} symbol - Symbol name
* @param {string} module - Module name
* @param {boolean} esModule
* @return {string}
*/
function generateImport(symbol, module, esModule = loaderDefaults.esModule) {
return esModule ?
`import ${symbol} from ${stringify(module)}` :
`var ${symbol} = require(${stringify(module)})`;
}
module.exports = generateImport;

View file

@ -0,0 +1,10 @@
/**
* Because of extract-text-webpack-plugin interop returns just absolute path to filepath
* @param {string} filepath
* @return {string}
*/
function generateSpritePlaceholder(filepath) {
return filepath;
}
module.exports = generateSpritePlaceholder;

View file

@ -0,0 +1,50 @@
let ConcatenatedModule;
try {
// eslint-disable-next-line global-require,import/no-unresolved,import/no-extraneous-dependencies
ConcatenatedModule = require('webpack/lib/optimize/ConcatenatedModule');
// eslint-disable-next-line no-empty
} catch (e) {}
/**
* Get all modules from main & child compilations.
* Merge modules from ConcatenatedModule (when webpack.optimize.ModuleConcatenationPlugin is used)
* @param {Compilation} compilation
* @return {NormalModule[]}
*/
function getAllModules(compilation) {
let modules = Array.from(compilation.modules);
// Look up in child compilations
if (compilation.children.length > 0) {
const childModules = compilation.children.map(getAllModules)
.reduce((acc, compilationModules) => acc.concat(compilationModules), []);
modules = modules.concat(childModules);
}
// Merge modules from ConcatenatedModule
if (ConcatenatedModule) {
const concatenatedModules = modules
.filter(m => m instanceof ConcatenatedModule)
.reduce((acc, m) => {
/**
* @see https://git.io/v7XDu
* In webpack@3.5.1 `modules` public property was removed
* To workaround this private `_orderedConcatenationList` property is used to collect modules
*/
const subModules = 'modules' in m
? m.modules
: m._orderedConcatenationList.map(entry => entry.module);
return acc.concat(subModules);
}, []);
if (concatenatedModules.length > 0) {
modules = modules.concat(concatenatedModules);
}
}
return modules;
}
module.exports = getAllModules;

View file

@ -0,0 +1,34 @@
const normalizeRule = require('./normalize-rule');
const isWebpack1 = require('./is-webpack-1');
/**
* webpack 1 compat loader options finder. Returns normalized options.
* @param {string} loaderPath
* @param {Object|Rule} rule
* @return {Object|null}
*/
function getLoaderOptions(loaderPath, rule) {
let multiRuleProp;
if (isWebpack1) {
multiRuleProp = 'loaders';
} else if (rule.oneOf) {
multiRuleProp = 'oneOf';
} else {
multiRuleProp = 'use';
}
const multiRule = typeof rule === 'object' && Array.isArray(rule[multiRuleProp]) ? rule[multiRuleProp] : null;
let options;
if (multiRule) {
const rules = [].concat(...multiRule.map(r => (r.use || r)));
options = rules.map(normalizeRule).find(r => loaderPath.includes(r.loader)).options;
} else {
options = normalizeRule(rule).options;
}
return options;
}
module.exports = getLoaderOptions;

View file

@ -0,0 +1,22 @@
/* eslint-disable import/no-unresolved */
// eslint-disable-next-line import/no-extraneous-dependencies
const RuleSet = require('webpack/lib/RuleSet');
const flattenAndExtractUse = rules => rules.reduce((pre, rule) => {
if ('rules' in rule || 'oneOf' in rule) {
return pre.concat(flattenAndExtractUse(rule.rules || rule.oneOf));
}
return pre.concat(rule.use || []);
}, []);
module.exports = (compiler) => {
const rawRules = compiler.options.module.rules;
const { rules } = new RuleSet(rawRules);
const rule = flattenAndExtractUse(rules)
.find((item) => {
return /svg-sprite-loader/.test(item.loader);
}) || {};
return rule.options || {};
};

View file

@ -0,0 +1,60 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-unresolved */
/* eslint-disable no-restricted-syntax */
// eslint-disable-next-line import/no-extraneous-dependencies
const BasicEffectRulePlugin = require('webpack/lib/rules/BasicEffectRulePlugin');
const BasicMatcherRulePlugin = require('webpack/lib/rules/BasicMatcherRulePlugin');
const RuleSetCompiler = require('webpack/lib/rules/RuleSetCompiler');
const UseEffectRulePlugin = require('webpack/lib/rules/UseEffectRulePlugin');
const ruleSetCompiler = new RuleSetCompiler([
new BasicMatcherRulePlugin('test', 'resource'),
new BasicMatcherRulePlugin('include', 'resource'),
new BasicMatcherRulePlugin('exclude', 'resource', true),
new BasicMatcherRulePlugin('resource'),
new BasicMatcherRulePlugin('conditions'),
new BasicMatcherRulePlugin('resourceQuery'),
new BasicMatcherRulePlugin('realResource'),
new BasicMatcherRulePlugin('issuer'),
new BasicMatcherRulePlugin('compiler'),
new BasicEffectRulePlugin('type'),
new BasicEffectRulePlugin('sideEffects'),
new BasicEffectRulePlugin('parser'),
new BasicEffectRulePlugin('resolve'),
new UseEffectRulePlugin()
]);
// const RuleSet = require('webpack/lib/RuleSet');
const flattenAndExtractUse = rules => rules.reduce((pre, rule) => {
// if ('rules' in rule || 'oneOf' in rule) {
// return pre.concat(flattenAndExtractUse(rule.rules || rule.oneOf));
// }
return pre.concat(rule || []);
}, []);
module.exports = (compiler) => {
const rawRules = compiler.options.module.rules;
const rulesUse = [];
for (const rawRule of rawRules) {
const clonedRawRule = Object.assign({}, rawRule);
delete clonedRawRule.include;
const ruleSet = ruleSetCompiler.compile([{
rules: [clonedRawRule]
}]);
rulesUse.push(ruleSet.exec({
resource: '.svg'
}));
}
// const {
// rules
// } = ruleSet;
const rule = flattenAndExtractUse(rulesUse)
.find((item) => {
return /svg-sprite-loader/.test(item.value.loader);
}) || {};
return rule.value ? rule.value.options : {};
};

View file

@ -0,0 +1,11 @@
/* eslint-disable global-require */
let getMatchedRule = null;
try {
getMatchedRule = require('./get-matched-rule-4');
} catch (e) {
getMatchedRule = require('./get-matched-rule-5');
}
module.exports = getMatchedRule;

View file

@ -0,0 +1,33 @@
/**
* Find nearest module chunk (not sure that is reliable method, but who cares).
* @see http://stackoverflow.com/questions/43202761/how-to-determine-all-module-chunks-in-webpack
* @param {NormalModule} module
* @param {NormalModule[]} modules - webpack 1 compat
* @return {Chunk|null}
*/
function getModuleChunk(module, modules) {
let chunks;
if (module.chunksIterable) {
chunks = Array.from(module.chunksIterable);
} else if (module.mapChunks) {
chunks = module.mapChunks();
} else {
chunks = module.chunks;
}
// webpack 1 compat
const issuer = typeof module.issuer === 'string'
? modules.find(m => m.request === module.issuer)
: module.issuer;
if (Array.isArray(chunks) && chunks.length > 0) {
return chunks[chunks.length - 1];
} else if (issuer) {
return getModuleChunk(issuer, modules);
}
return null;
}
module.exports = getModuleChunk;

View file

@ -0,0 +1,16 @@
module.exports.generateExport = require('./generate-export');
module.exports.generateImport = require('./generate-import');
module.exports.generateSpritePlaceholder = require('./generate-sprite-placeholder');
module.exports.getAllModules = require('./get-all-modules');
// module.exports.getLoaderOptions = require('./get-loader-options');
module.exports.getModuleChunk = require('./get-module-chunk');
module.exports.getMatchedRule = require('./get-matched-rule');
module.exports.isModuleShouldBeExtracted = require('./is-module-should-be-extracted');
module.exports.interpolate = require('./interpolate');
module.exports.isWebpack1 = require('./is-webpack-1');
module.exports.MappedList = require('./mapped-list');
module.exports.normalizeRule = require('./normalize-rule');
module.exports.replaceInModuleSource = require('./replace-in-module-source');
module.exports.replaceSpritePlaceholder = require('./replace-sprite-placeholder');
module.exports.stringify = require('./stringify');
module.exports.stringifySymbol = require('./stringify-symbol');

View file

@ -0,0 +1,15 @@
const { interpolateName } = require('loader-utils');
/**
* @param {string} pattern
* @param {Object} options
* @param {string} options.resourcePath
* @param {string} [options.context]
* @param {string} [options.content]
*/
function interpolate(pattern, options) {
const { resourcePath, context, content } = options;
return interpolateName({ resourcePath }, pattern, { context, content });
}
module.exports = interpolate;

View file

@ -0,0 +1,35 @@
const defaults = require('../config');
const normalizeRule = require('./normalize-rule');
const spriteLoaderPath = require.resolve('../loader');
/**
* @param {NormalModule} module
* @return {boolean}
*/
function isModuleShouldBeExtracted(module) {
const { request, issuer, loaders } = module;
let rule = null;
if (Array.isArray(loaders) && loaders.length > 0) {
// Find loader rule
rule = loaders.map(normalizeRule).find(data => data.loader === spriteLoaderPath);
}
let issuerResource = null;
if (issuer) {
// webpack 1 compat
issuerResource = typeof issuer === 'string' ? issuer : issuer.resource;
}
if (typeof request === 'string' && (!request.includes(spriteLoaderPath) || !rule)) {
return false;
}
return !!(
(issuer && defaults.EXTRACTABLE_MODULE_ISSUER_PATTERN.test(issuerResource)) ||
(rule && rule.options && rule.options.extract)
);
}
module.exports = isModuleShouldBeExtracted;

View file

@ -0,0 +1,6 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const webpackPkg = require('webpack/package.json');
const webpackMajorVersion = parseInt(webpackPkg.version.split('.')[0], 10);
module.exports = webpackMajorVersion === 1;

View file

@ -0,0 +1,147 @@
// TODO refactor this smelly code!
const loaderDefaults = require('../config').loader;
const getAllModules = require('./get-all-modules');
const isModuleShouldBeExtracted = require('./is-module-should-be-extracted');
const getModuleChunk = require('./get-module-chunk');
const interpolate = require('./interpolate');
const getMatchedRule = require('./get-matched-rule');
class MappedListItem {
/**
* @param {SpriteSymbol} symbol
* @param {NormalModule} module
* @param {string} spriteFilename
*/
constructor(symbol, module, spriteFilename) {
this.symbol = symbol;
this.module = module;
this.resource = symbol.request.file;
this.spriteFilename = spriteFilename;
}
get url() {
return `${this.spriteFilename}#${this.symbol.id}`;
}
get useUrl() {
return `${this.spriteFilename}#${this.symbol.useId}`;
}
}
class MappedList {
/**
* @param {SpriteSymbol[]} symbols
* @param {Compilation} compilation
*/
constructor(symbols, compilation, shouldLog = false) {
const { compiler } = compilation;
this.symbols = symbols;
this.rule = getMatchedRule(compiler);
this.allModules = getAllModules(compilation);
this.spriteModules = this.allModules.filter(isModuleShouldBeExtracted);
this.shouldLog = shouldLog;
this.items = this.create();
}
/**
* @param {MappedListItem[]} data
* @return {Object<string, MappedListItem>}
*/
static groupItemsBySpriteFilename(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 groupItemsBySymbolFile(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, rule } = this;
const data = symbols.reduce((acc, symbol) => {
const resource = symbol.request.file;
const module = spriteModules.find((m) => {
return 'resource' in m ? m.resource.split('?')[0] === resource : false;
});
let spriteFilename = rule.spriteFilename || loaderDefaults.spriteFilename;
const chunk = module ? getModuleChunk(module, allModules) : null;
if (typeof spriteFilename !== 'function' && chunk && chunk.name) {
spriteFilename = spriteFilename.replace('[chunkname]', chunk.name);
} else if (typeof spriteFilename === 'function') {
spriteFilename = spriteFilename(resource);
}
if (rule && module) {
acc.push(new MappedListItem(symbol, module, spriteFilename));
}
return acc;
}, []);
// Additional pass to interpolate [hash] in spriteFilename
const itemsBySpriteFilename = MappedList.groupItemsBySpriteFilename(data);
const filenames = Object.keys(itemsBySpriteFilename);
filenames.forEach((filename) => {
if (!filename.includes('hash')) {
return;
}
const items = itemsBySpriteFilename[filename];
const spriteSymbols = items.map(item => item.symbol);
const content = spriteSymbols.map(s => s.render()).join('');
const interpolatedName = interpolate(filename, {
resourcePath: filename,
content
});
items
.filter(item => item.spriteFilename !== interpolatedName)
.forEach(item => item.spriteFilename = interpolatedName);
});
return data;
}
/**
* @return {Object<string, MappedListItem>}
*/
groupItemsBySpriteFilename() {
return MappedList.groupItemsBySpriteFilename(this.items);
}
/**
* @param {Function} [mapper] Custom grouper function
* @return {Object<string, MappedListItem>}
*/
groupItemsBySymbolFile(mapper) {
return MappedList.groupItemsBySymbolFile(this.items, mapper);
}
}
module.exports = MappedList;

View file

@ -0,0 +1,33 @@
const { parseQuery } = require('loader-utils');
const isWebpack1 = require('./is-webpack-1');
/**
* webpack 1 compat rule normalizer
* @param {string|Rule} rule (string - webpack 1, Object - webpack 2)
* @return {Object<loader: string, options: Object|null>}
*/
function normalizeRule(rule) {
if (!rule) {
throw new Error('Rule should be string or object');
}
let data;
if (typeof rule === 'string') {
const parts = rule.split('?');
data = {
loader: parts[0],
options: parts[1] ? parseQuery(`?${parts[1]}`) : null
};
} else {
const options = isWebpack1 ? rule.query : rule.options;
data = {
loader: rule.loader,
options: options || null
};
}
return data;
}
module.exports = normalizeRule;

View file

@ -0,0 +1,20 @@
const replaceSpritePlaceholder = require('./replace-sprite-placeholder');
/**
* @param {NormalModule|ExtractedModule} module
* @param {Object<string, string>} replacements
* @return {NormalModule|ExtractedModule}
*/
function replaceInModuleSource(module, replacements) {
const source = module._source;
if (typeof source === 'string') {
module._source = replaceSpritePlaceholder(source, replacements);
} else if (typeof source === 'object' && typeof source._value === 'string') {
source._value = replaceSpritePlaceholder(source._value, replacements);
}
return module;
}
module.exports = replaceInModuleSource;

View file

@ -0,0 +1,26 @@
const escapeRegExpSpecialChars = require('escape-string-regexp');
const isWindows = /^win/i.test(process.platform);
/**
* @param {string} content
* @param {Object<string, string>} replacements
* @return {string}
*/
function replaceSpritePlaceholder(content, replacements) {
let result = content;
Object.keys(replacements)
.forEach((subj) => {
let re = new RegExp(escapeRegExpSpecialChars(subj), 'g');
result = result.replace(re, replacements[subj]);
if (isWindows) {
re = new RegExp(escapeRegExpSpecialChars(subj), 'g');
result = result.replace(/\\\\/g, '\\').replace(re, replacements[subj]);
}
});
return result;
}
module.exports = replaceSpritePlaceholder;

View file

@ -0,0 +1,16 @@
const stringify = require('./stringify');
/**
* @param {SpriteSymbol} symbol
* @return {string}
*/
function stringifySymbol(symbol) {
return stringify({
id: symbol.id,
use: symbol.useId,
viewBox: symbol.viewBox,
content: symbol.render()
});
}
module.exports = stringifySymbol;

View file

@ -0,0 +1,15 @@
const stringifiedRegexp = /^'|".*'|"$/;
/**
* If already stringified - return original content
* @param {Object|Array} content
* @return {string}
*/
function stringify(content) {
if (typeof content === 'string' && stringifiedRegexp.test(content)) {
return content;
}
return JSON.stringify(content, null, 2);
}
module.exports = stringify;