chushihua

This commit is contained in:
li
2026-01-26 23:20:48 +08:00
commit 7b5aab0206
22521 changed files with 3300090 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
'use strict';
const path = require('path');
const postcss = require('postcss');
const declProcessor = require('./lib/decl-processor').declProcessor;
/**
*
* @type {Plugin}
*/
module.exports = postcss.plugin('postcss-url', (options) => {
options = options || {};
return function(styles, result) {
const opts = result.opts;
const from = opts.from ? path.dirname(opts.from) : '.';
const to = opts.to ? path.dirname(opts.to) : from;
styles.walkDecls((decl) =>
declProcessor(from, to, options, result, decl)
);
};
});
/**
* @callback PostcssUrl~UrlProcessor
* @param {String} from from
* @param {String} dirname to dirname
* @param {String} oldUrl url
* @param {String} to destination
* @param {Object} options plugin options
* @param {Object} decl postcss declaration
* @return {String|undefined} new url or undefined if url is old
*/
/**
* @typedef {Object} PostcssUrl~HashOptions
* @property {Function|String} [method=^xxhash32|xxhash64] - hash name or custom function, accepting file content
* @see https://github.com/pierrec/js-xxhash
* @property {Number} [shrink=8] - shrink hash string
*/
/**
* @typedef {Object} Decl - postcss decl
* @see http://api.postcss.org/Declaration.html
*/
/**
* @typedef {Object} Result - postcss result
* @see http://api.postcss.org/Result.html
*/
+151
View File
@@ -0,0 +1,151 @@
'use strict';
const matchOptions = require('./match-options');
const paths = require('./paths');
const getPathDeclFile = paths.getPathDeclFile;
const getDirDeclFile = paths.getDirDeclFile;
const prepareAsset = paths.prepareAsset;
/**
* @typedef UrlRegExp
* @name UrlRegExp
* @desc A regex for match url with parentheses:
* (before url)(the url)(after url).
* (the url) will be replace with new url, and before and after will remain
* @type RegExp
*/
/**
* @type {UrlRegExp[]}
*/
const URL_PATTERNS = [
/(url\(\s*['"]?)([^"')]+)(["']?\s*\))/g,
/(AlphaImageLoader\(\s*src=['"]?)([^"')]+)(["'])/g
];
const WITH_QUOTES = /^['"]/;
/**
* Restricted modes
*
* @type {String[]}
*/
const PROCESS_TYPES = ['rebase', 'inline', 'copy', 'custom'];
const getUrlProcessorType = (optionUrl) =>
typeof optionUrl === 'function' ? 'custom' : (optionUrl || 'rebase');
/**
* @param {String} optionUrl
* @returns {PostcssUrl~UrlProcessor}
*/
function getUrlProcessor(optionUrl) {
const mode = getUrlProcessorType(optionUrl);
if (PROCESS_TYPES.indexOf(mode) === -1) {
throw new Error(`Unknown mode for postcss-url: ${mode}`);
}
return require(`../type/${mode}`);
}
/**
* @param {PostcssUrl~UrlProcessor} urlProcessor
* @param {Result} result
* @param {Decl} decl
* @returns {Function}
*/
const wrapUrlProcessor = (urlProcessor, result, decl) => {
const warn = (message) => decl.warn(result, message);
const addDependency = (file) => result.messages.push({
type: 'dependency',
file,
parent: getPathDeclFile(decl)
});
return (asset, dir, option) =>
urlProcessor(asset, dir, option, decl, warn, result, addDependency);
};
/**
* @param {Decl} decl
* @returns {RegExp}
*/
const getPattern = (decl) =>
URL_PATTERNS.find((pattern) => pattern.test(decl.value));
/**
* @param {String} url
* @param {Dir} dir
* @param {Options} options
* @param {Result} result
* @param {Decl} decl
* @returns {String|undefined}
*/
const replaceUrl = (url, dir, options, result, decl) => {
const asset = prepareAsset(url, dir, decl);
const matchedOptions = matchOptions(asset, options);
if (!matchedOptions) return;
const process = (option) => {
const wrappedUrlProcessor = wrapUrlProcessor(getUrlProcessor(option.url), result, decl);
return wrappedUrlProcessor(asset, dir, option);
};
if (Array.isArray(matchedOptions)) {
matchedOptions.forEach((option) => asset.url = process(option));
} else {
asset.url = process(matchedOptions);
}
return asset.url;
};
/**
* @param {String} from
* @param {String} to
* @param {PostcssUrl~Options} options
* @param {Result} result
* @param {Decl} decl
* @returns {PostcssUrl~DeclProcessor}
*/
const declProcessor = (from, to, options, result, decl) => {
const dir = { from, to, file: getDirDeclFile(decl) };
const pattern = getPattern(decl);
if (!pattern) return;
decl.value = decl.value
.replace(pattern, (matched, before, url, after) => {
const newUrl = replaceUrl(url, dir, options, result, decl);
if (!newUrl) return matched;
if (WITH_QUOTES.test(newUrl) && WITH_QUOTES.test(after)) {
before = before.slice(0, -1);
after = after.slice(1);
}
return `${before}${newUrl}${after}`;
});
};
module.exports = {
replaceUrl,
declProcessor
};
/**
* @typedef {Object} PostcssUrl~Options - postcss-url Options
* @property {String} [url=^rebase|inline|copy|custom] - processing mode
* @property {Minimatch|RegExp|Function} [filter] - filter assets by relative pathname
* @property {String} [assetsPath] - absolute or relative path to copy assets
* @property {String|String[]} [basePath] - absolute or relative paths to search, when copy or inline
* @property {Number} [maxSize] - max file size in kbytes for inline mode
* @property {String} [fallback] - fallback mode if file exceeds maxSize
* @property {Boolean} [useHash] - use file hash instead filename
* @property {HashOptions} [hashOptions] - params for generating hash name
*/
+55
View File
@@ -0,0 +1,55 @@
'use strict';
/**
* Optimize encoding SVG files (IE9+, Android 3+)
* @see https://codepen.io/tigt/post/optimizing-svgs-in-data-uris
*
* @param {String} svgContent
* @returns {String}
*/
const optimizedSvgEncode = (svgContent) => {
const result = encodeURIComponent(svgContent)
.replace(/%3D/g, '=')
.replace(/%3A/g, ':')
.replace(/%2F/g, '/')
.replace(/%22/g, "'")
.replace(/%2C/g, ',')
.replace(/%3B/g, ';');
// Lowercase the hex-escapes for better gzipping
return result.replace(/(%[0-9A-Z]{2})/g, (matched, AZ) => {
return AZ.toLowerCase();
});
};
/**
* Encoding file contents to string
*
* @param {PostcssUrl~File} file
* @param {String} [encodeType=base64|encodeURI|encodeURIComponent]
* @param {Boolean} [shouldOptimizeURIEncode]
* @returns {string}
*/
module.exports = (file, encodeType, shouldOptimizeSvgEncode) => {
const dataMime = `data:${file.mimeType}`;
if (encodeType === 'base64') {
return `${dataMime};base64,${file.contents.toString('base64')}`;
}
const encodeFunc = encodeType === 'encodeURI' ? encodeURI : encodeURIComponent;
const content = file.contents.toString('utf8')
// removing new lines
.replace(/\n+/g, '');
let encodedStr = (shouldOptimizeSvgEncode && encodeType === 'encodeURIComponent')
? optimizedSvgEncode(content)
: encodeFunc(content);
encodedStr = encodedStr
.replace(/%20/g, ' ')
.replace(/#/g, '%23');
return `${dataMime},${encodedStr}`;
};
+42
View File
@@ -0,0 +1,42 @@
'use strict';
const fs = require('fs');
const mime = require('mime');
const getPathByBasePath = require('./paths').getPathByBasePath;
/**
*
* @param {PostcssUrl~Asset} asset
* @param {PostcssUrl~Options} options
* @param {PostcssUrl~Dir} dir
* @param {Function} warn
* @returns {PostcssUrl~File}
*/
const getFile = (asset, options, dir, warn) => {
const paths = options.basePath
? getPathByBasePath(options.basePath, dir.from, asset.pathname)
: [asset.absolutePath];
const filePath = paths.find(fs.existsSync);
if (!filePath) {
warn(`Can't read file '${paths.join()}', ignoring`);
return;
}
return {
path: filePath,
contents: fs.readFileSync(filePath),
mimeType: mime.lookup(filePath)
};
};
module.exports = getFile;
/**
* @typedef {Object} PostcssUrl~File
* @property {String} path
* @property {Buffer} contents
* @property {String} mimeType
*/
+54
View File
@@ -0,0 +1,54 @@
'use strict';
const crypto = require('crypto');
const xxh = require('xxhashjs');
const HEXBASE = 16;
const defaultHashOptions = {
method: 'xxhash32',
shrink: 8,
append: false
};
const getxxhash = (content, options) => {
const hashFunc = options.method === 'xxhash32' ? xxh.h32 : xxh.h64;
const seed = 0;
return hashFunc(seed)
.update(content)
.digest()
.toString(HEXBASE);
};
const getHash = (content, options) => {
if (options.method && typeof options.method === 'function') {
return options.method(content);
}
if (options.method && options.method.indexOf('xxhash') === 0) {
return getxxhash(content, options);
}
try {
const hashFunc = crypto.createHash(options.method);
return hashFunc.update(content)
.digest('hex');
} catch (e) {
return null;
}
};
module.exports = function(content, options) {
options = options || defaultHashOptions;
let hash = getHash(content, options);
if (hash == null) {
// bad hash method; fallback to defaults
// TODO: warning/error reporting?
hash = getHash(content, defaultHashOptions);
}
return options.shrink ? hash.substr(0, options.shrink) : hash;
};
+92
View File
@@ -0,0 +1,92 @@
'use strict';
const minimatch = require('minimatch');
const path = require('path');
const paths = require('./paths');
const isUrlShouldBeIgnored = paths.isUrlShouldBeIgnored;
/**
* Returns whether the given asset matches the given pattern
* Allways returns true if the given pattern is empty
*
* @param {PostcssUrl~Asset} asset the processed asset
* @param {String|RegExp|Function} pattern A minimatch string,
* regular expression or function to test the asset
*
* @returns {Boolean}
*/
const matchesFilter = (asset, pattern) => {
const relativeToRoot = path.relative(process.cwd(), asset.absolutePath);
if (typeof pattern === 'string') {
pattern = minimatch.filter(pattern);
return pattern(relativeToRoot);
}
if (pattern instanceof RegExp) {
return pattern.test(relativeToRoot);
}
if (pattern instanceof Function) {
return pattern(asset);
}
return true;
};
/**
* Matching single option
*
* @param {PostcssUrl~Asset} asset
* @param {PostcssUrl~Options} option
* @returns {Boolean}
*/
const matchOption = (asset, option) => {
const matched = matchesFilter(asset, option.filter);
if (!matched) return false;
return typeof option.url === 'function' || !isUrlShouldBeIgnored(asset.url, option);
};
const isMultiOption = (option) =>
option.multi && typeof option.url === 'function';
/**
* Matching options by asset
*
* @param {PostcssUrl~Asset} asset
* @param {PostcssUrl~Options|PostcssUrl~Options[]} options
* @returns {PostcssUrl~Options|undefined}
*/
const matchOptions = (asset, options) => {
if (!options) return;
if (Array.isArray(options)) {
const optionIndex = options.findIndex((option) => matchOption(asset, option));
if (optionIndex < 0) return;
const matchedOption = options[optionIndex];
// if founded option is last
if (optionIndex === options.length - 1) return matchedOption;
const extendOptions = options
.slice(optionIndex + 1)
.filter((option) =>
(isMultiOption(matchedOption) || isMultiOption(option)) && matchOption(asset, option)
);
return extendOptions.length
? [matchedOption].concat(extendOptions)
: matchedOption;
}
if (matchOption(asset, options)) return options;
};
module.exports = matchOptions;
+155
View File
@@ -0,0 +1,155 @@
'use strict';
const path = require('path');
const url = require('url');
/**
* Normalazing result url, before replace decl value
*
* @param {String} assetUrl
* @returns {String}
*/
const normalize = (assetUrl) => {
assetUrl = path.normalize(assetUrl);
return path.sep === '\\' ? assetUrl.replace(/\\/g, '\/') : assetUrl;
};
/**
* @param {String} assetUrl
* @returns {Boolean}
*/
const isUrlWithoutPathname = (assetUrl) => {
return assetUrl[0] === '#'
|| assetUrl.indexOf('%23') === 0
|| assetUrl.indexOf('data:') === 0
|| /^[a-z]+:\/\//.test(assetUrl)
|| /^\/\//.test(assetUrl);
};
/**
* Check if url is absolute, hash or data-uri
*
* @param {String} assetUrl
* @param {PostcssUrl~Options} options
* @returns {Boolean}
*/
const isUrlShouldBeIgnored = (assetUrl, options) => {
const isAbsolutePath = assetUrl[0] === '/';
const isStartsWithTilde = assetUrl[0] === '~';
return isUrlWithoutPathname(assetUrl) || ((isAbsolutePath || isStartsWithTilde) && !options.basePath);
};
/**
* @param {String} baseDir - absolute target path
* @param {String} assetsPath - extend asset path, can be absolute path
* @param {String} relative - current relative asset path
* @returns {String}
*/
const getAssetsPath = (baseDir, assetsPath, relative) =>
path.resolve(baseDir, assetsPath || '', relative || '');
/**
* Target path, output base dir
*
* @param {Dir} dir
* @returns {String}
*/
const getTargetDir = (dir) =>
dir.from !== dir.to ? dir.to : process.cwd();
/**
* Stylesheet file path from decl
*
* @param {Decl} decl
* @returns {String}
*/
const getPathDeclFile = (decl) =>
decl.source && decl.source.input && decl.source.input.file;
/**
* Stylesheet file dir from decl
*
* @param {Decl} decl
* @returns {String}
*/
const getDirDeclFile = (decl) => {
const filename = getPathDeclFile(decl);
return filename ? path.dirname(filename) : process.cwd();
};
/**
* Returns paths list, where we can find assets file
*
* @param {String[]|String} basePath - base paths where trying search to assets file
* @param {Dir} dirFrom
* @param {String} relPath - relative asset path
* @returns {String[]}
*/
const getPathByBasePath = (basePath, dirFrom, relPath) => {
if (relPath[0] === '/') {
relPath = `.${relPath}`;
}
basePath = !Array.isArray(basePath) ? [basePath] : basePath;
return basePath.map((pathItem) =>
getAssetsPath(dirFrom, pathItem, relPath)
);
};
/**
* Preparing asset paths and data
*
* @param {String} assetUrl
* @param {PostcssUrl~Dir} dir
* @param {Decl} decl
* @returns {PostcssUrl~Asset}
*/
const prepareAsset = (assetUrl, dir, decl) => {
const parsedUrl = url.parse(assetUrl);
const pathname = !isUrlWithoutPathname(assetUrl) ? parsedUrl.pathname : null;
const absolutePath = pathname
? path.resolve(path.join(dir.file, pathname))
: getPathDeclFile(decl);
return {
url: assetUrl,
originUrl: assetUrl,
pathname,
absolutePath: absolutePath || dir.from,
relativePath: absolutePath ? path.relative(dir.from, absolutePath) : '.',
search: (parsedUrl.search || ''),
hash: (parsedUrl.hash || '')
};
};
module.exports = {
normalize,
prepareAsset,
getAssetsPath,
getDirDeclFile,
getPathDeclFile,
getTargetDir,
getPathByBasePath,
isUrlShouldBeIgnored
};
/**
* @typedef {Object} PostcssUrl~Asset
* @property {String} url - origin asset url
* @property {String} name - parsed asset filename
* @property {String} absolutePath - absolute asset path
* @property {String} relativePath - relative asset path (relative to target dir)
* @property {String} search - search from url, ex. ?query=1
* @property {String} hash - hash from url
*/
/**
* @typedef {Object} PostcssUrl~Dir
* @property {String} from - dirname from postcss option 'from'
* @property {String} to - dirname from postcss option 'to'
* @property {String} file - decl file dirname (css file)
*/
+69
View File
@@ -0,0 +1,69 @@
'use strict';
const path = require('path');
const fs = require('fs');
const mkdirp = require('mkdirp');
const calcHash = require('../lib/hash');
const paths = require('../lib/paths');
const getFile = require('../lib/get-file');
const getTargetDir = paths.getTargetDir;
const getAssetsPath = paths.getAssetsPath;
const normalize = paths.normalize;
const getHashName = (file, options) =>
(options && options.append ? (`${path.basename(file.path, path.extname(file.path))}_`) : '')
+ calcHash(file.contents, options)
+ path.extname(file.path);
/**
* Copy images from readed from url() to an specific assets destination
* (`assetsPath`) and fix url() according to that path.
* You can rename the assets by a hash or keep the real filename.
*
* Option assetsPath is require and is relative to the css destination (`to`)
*
* @type {PostcssUrl~UrlProcessor}
* @param {PostcssUrl~Asset} asset
* @param {PostcssUrl~Dir} dir
* @param {PostcssUrl~Option} options
* @param {PostcssUrl~Decl} decl
* @param {Function} warn
* @param {Result} result
* @param {Function} addDependency
*
* @returns {String|Undefined}
*/
module.exports = function processCopy(asset, dir, options, decl, warn, result, addDependency) {
if (!options.assetsPath && dir.from === dir.to) {
warn('Option `to` of postcss is required, ignoring');
return;
}
const file = getFile(asset, options, dir, warn);
if (!file) return;
const assetRelativePath = options.useHash
? getHashName(file, options.hashOptions)
: asset.relativePath;
const targetDir = getTargetDir(dir);
const newAssetBaseDir = getAssetsPath(targetDir, options.assetsPath);
const newAssetPath = path.join(newAssetBaseDir, assetRelativePath);
const newRelativeAssetPath = normalize(
path.relative(targetDir, newAssetPath)
);
mkdirp.sync(path.dirname(newAssetPath));
if (!fs.existsSync(newAssetPath)) {
fs.writeFileSync(newAssetPath, file.contents);
}
addDependency(file.path);
return `${newRelativeAssetPath}${asset.search}${asset.hash}`;
};
+13
View File
@@ -0,0 +1,13 @@
/**
* Transform url() based on a custom callback
*
* @type {PostcssUrl~UrlProcessor}
* @param {PostcssUrl~Asset} asset
* @param {PostcssUrl~Dir} dir
* @param {PostcssUrl~Option} options
*
* @returns {String|Undefined}
*/
module.exports = function getCustomProcessor(asset, dir, options) {
return options.url.apply(null, arguments);
};
+87
View File
@@ -0,0 +1,87 @@
'use strict';
const fs = require('fs');
const processCopy = require('./copy');
const processRebase = require('./rebase');
const encodeFile = require('../lib/encode');
const getFile = require('../lib/get-file');
/**
* @param {String} originUrl
* @param {PostcssUrl~Dir} dir
* @param {PostcssUrl~Option} options
*
* @returns {String|Undefined}
*/
function processFallback(originUrl, dir, options) {
if (typeof options.fallback === 'function') {
return options.fallback.apply(null, arguments);
}
switch (options.fallback) {
case 'copy':
return processCopy.apply(null, arguments);
case 'rebase':
return processRebase.apply(null, arguments);
default:
return;
}
}
/**
* Inline image in url()
*
* @type {PostcssUrl~UrlProcessor}
* @param {PostcssUrl~Asset} asset
* @param {PostcssUrl~Dir} dir
* @param {PostcssUrl~Options} options
* @param {PostcssUrl~Decl} decl
* @param {Function} warn
* @param {Result} result
* @param {Function} addDependency
*
* @returns {String|Undefined}
*/
module.exports = function(asset, dir, options, decl, warn, result, addDependency) {
const file = getFile(asset, options, dir, warn);
if (!file) return;
if (!file.mimeType) {
warn(`Unable to find asset mime-type for ${file.path}`);
return;
}
const maxSize = (options.maxSize || 0) * 1024;
if (maxSize) {
const stats = fs.statSync(file.path);
if (stats.size >= maxSize) {
return processFallback.apply(this, arguments);
}
}
const isSvg = file.mimeType === 'image/svg+xml';
const defaultEncodeType = isSvg ? 'encodeURIComponent' : 'base64';
const encodeType = options.encodeType || defaultEncodeType;
// Warn for svg with hashes/fragments
if (isSvg && asset.hash && !options.ignoreFragmentWarning) {
// eslint-disable-next-line max-len
warn(`Image type is svg and link contains #. Postcss-url cant handle svg fragments. SVG file fully inlined. ${file.path}`);
}
addDependency(file.path);
const optimizeSvgEncode = isSvg && options.optimizeSvgEncode;
const encodedStr = encodeFile(file, encodeType, optimizeSvgEncode);
const resultValue = options.includeUriFragment && asset.hash
? encodedStr + asset.hash
: encodedStr;
// wrap url by quotes if percent-encoded svg
return isSvg && encodeType !== 'base64' ? `"${resultValue}"` : resultValue;
};
+25
View File
@@ -0,0 +1,25 @@
'use strict';
const path = require('path');
const paths = require('../lib/paths');
const normalize = paths.normalize;
const getAssetsPath = paths.getAssetsPath;
/**
* Fix url() according to source (`from`) or destination (`to`)
*
* @type {PostcssUrl~UrlProcessor}
* @param {PostcssUrl~Asset} asset
* @param {PostcssUrl~Dir} dir
* @param {PostcssUrl~Option} options
*
* @returns {String|Undefined}
*/
module.exports = function(asset, dir, options) {
const dest = getAssetsPath(dir.to, options && options.assetsPath || '');
const rebasedUrl = normalize(
path.relative(dest, asset.absolutePath)
);
return `${rebasedUrl}${asset.search}${asset.hash}`;
};