Merge remote-tracking branch 'upstream/main'

This commit is contained in:
2026-08-26 09:31:34 +08:00
parent 9b8c4bfab6
commit 394eb0285d
34137 changed files with 3589424 additions and 0 deletions
+14
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;
+16
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;
@@ -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;
+50
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 = 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;
@@ -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;
@@ -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 || {};
};
@@ -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 : {};
};
+14
View File
@@ -0,0 +1,14 @@
/* eslint-disable global-require */
const getWebpackVersion = require('./get-webpack-version');
let getMatchedRule = null;
if (getWebpackVersion.IS_5) {
// webpack5 and upper
getMatchedRule = require('./get-matched-rule-5');
} else {
// webpack4 and lower
getMatchedRule = require('./get-matched-rule-4');
}
module.exports = getMatchedRule;
+33
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;
@@ -0,0 +1,21 @@
// eslint-disable-next-line import/no-extraneous-dependencies
// require.main is undefined while running WebStorm code assistance analyzer of webpack.config.js
if (!require.main) {
require.main = { require };
}
const webpackVersion = require.main.require('webpack/package.json').version;
/**
* @param {boolean} [onlyMajor=true]
* @return {string}
*/
function getWebpackVersion(onlyMajor = true) {
return onlyMajor ? webpackVersion.split('.')[0] : webpackVersion;
}
getWebpackVersion.IS_1 = getWebpackVersion() === '1';
getWebpackVersion.IS_2 = getWebpackVersion() === '2';
getWebpackVersion.IS_3 = getWebpackVersion() === '3';
getWebpackVersion.IS_4 = getWebpackVersion() === '4';
getWebpackVersion.IS_5 = getWebpackVersion() === '5';
module.exports = getWebpackVersion;
+17
View File
@@ -0,0 +1,17 @@
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.getWebpackVersion = require('./get-webpack-version');
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');
+15
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;
@@ -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;
+6
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;
+147
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;
+33
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;
@@ -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;
@@ -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;
+16
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;
+15
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;