mirror of https://github.com/webpack/webpack.git
				
				
				
			refactor: no extra work for CSS unescaping
This commit is contained in:
		
						commit
						644f1d1271
					
				|  | @ -99,6 +99,136 @@ const normalizeUrl = (str, isString) => { | |||
| 	return str; | ||||
| }; | ||||
| 
 | ||||
| // eslint-disable-next-line no-useless-escape
 | ||||
| const regexSingleEscape = /[ -,.\/:-@[\]\^`{-~]/; | ||||
| const regexExcessiveSpaces = | ||||
| 	/(^|\\+)?(\\[A-F0-9]{1,6})\u0020(?![a-fA-F0-9\u0020])/g; | ||||
| 
 | ||||
| /** | ||||
|  * @param {string} str string | ||||
|  * @returns {string} escaped identifier | ||||
|  */ | ||||
| const escapeIdentifier = str => { | ||||
| 	let output = ""; | ||||
| 	let counter = 0; | ||||
| 
 | ||||
| 	while (counter < str.length) { | ||||
| 		const character = str.charAt(counter++); | ||||
| 
 | ||||
| 		let value; | ||||
| 
 | ||||
| 		if (/[\t\n\f\r\u000B]/.test(character)) { | ||||
| 			const codePoint = character.charCodeAt(0); | ||||
| 
 | ||||
| 			value = `\\${codePoint.toString(16).toUpperCase()} `; | ||||
| 		} else if (character === "\\" || regexSingleEscape.test(character)) { | ||||
| 			value = `\\${character}`; | ||||
| 		} else { | ||||
| 			value = character; | ||||
| 		} | ||||
| 
 | ||||
| 		output += value; | ||||
| 	} | ||||
| 
 | ||||
| 	const firstChar = str.charAt(0); | ||||
| 
 | ||||
| 	if (/^-[-\d]/.test(output)) { | ||||
| 		output = `\\-${output.slice(1)}`; | ||||
| 	} else if (/\d/.test(firstChar)) { | ||||
| 		output = `\\3${firstChar} ${output.slice(1)}`; | ||||
| 	} | ||||
| 
 | ||||
| 	// Remove spaces after `\HEX` escapes that are not followed by a hex digit,
 | ||||
| 	// since they’re redundant. Note that this is only possible if the escape
 | ||||
| 	// sequence isn’t preceded by an odd number of backslashes.
 | ||||
| 	output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => { | ||||
| 		if ($1 && $1.length % 2) { | ||||
| 			// It’s not safe to remove the space, so don’t.
 | ||||
| 			return $0; | ||||
| 		} | ||||
| 
 | ||||
| 		// Strip the space.
 | ||||
| 		return ($1 || "") + $2; | ||||
| 	}); | ||||
| 
 | ||||
| 	return output; | ||||
| }; | ||||
| 
 | ||||
| const CONTAINS_ESCAPE = /\\/; | ||||
| 
 | ||||
| /** | ||||
|  * @param {string} str string | ||||
|  * @returns {[string, number] | undefined} hex | ||||
|  */ | ||||
| const gobbleHex = str => { | ||||
| 	const lower = str.toLowerCase(); | ||||
| 	let hex = ""; | ||||
| 	let spaceTerminated = false; | ||||
| 
 | ||||
| 	for (let i = 0; i < 6 && lower[i] !== undefined; i++) { | ||||
| 		const code = lower.charCodeAt(i); | ||||
| 		// check to see if we are dealing with a valid hex char [a-f|0-9]
 | ||||
| 		const valid = (code >= 97 && code <= 102) || (code >= 48 && code <= 57); | ||||
| 		// https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
 | ||||
| 		spaceTerminated = code === 32; | ||||
| 		if (!valid) break; | ||||
| 		hex += lower[i]; | ||||
| 	} | ||||
| 
 | ||||
| 	if (hex.length === 0) return undefined; | ||||
| 
 | ||||
| 	const codePoint = Number.parseInt(hex, 16); | ||||
| 	const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff; | ||||
| 
 | ||||
| 	// Add special case for
 | ||||
| 	// "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
 | ||||
| 	// https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
 | ||||
| 	if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) { | ||||
| 		return ["\uFFFD", hex.length + (spaceTerminated ? 1 : 0)]; | ||||
| 	} | ||||
| 
 | ||||
| 	return [ | ||||
| 		String.fromCodePoint(codePoint), | ||||
| 		hex.length + (spaceTerminated ? 1 : 0) | ||||
| 	]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * @param {string} str string | ||||
|  * @returns {string} unescaped string | ||||
|  */ | ||||
| const unescapeIdentifier = str => { | ||||
| 	const needToProcess = CONTAINS_ESCAPE.test(str); | ||||
| 	if (!needToProcess) return str; | ||||
| 	let ret = ""; | ||||
| 	for (let i = 0; i < str.length; i++) { | ||||
| 		if (str[i] === "\\") { | ||||
| 			const gobbled = gobbleHex(str.slice(i + 1, i + 7)); | ||||
| 			if (gobbled !== undefined) { | ||||
| 				ret += gobbled[0]; | ||||
| 				i += gobbled[1]; | ||||
| 				continue; | ||||
| 			} | ||||
| 			// Retain a pair of \\ if double escaped `\\\\`
 | ||||
| 			// https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e
 | ||||
| 			if (str[i + 1] === "\\") { | ||||
| 				ret += "\\"; | ||||
| 				i += 1; | ||||
| 				continue; | ||||
| 			} | ||||
| 			// if \\ is at the end of the string retain it
 | ||||
| 			// https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb
 | ||||
| 			if (str.length === i + 1) { | ||||
| 				ret += str[i]; | ||||
| 			} | ||||
| 			continue; | ||||
| 		} | ||||
| 		ret += str[i]; | ||||
| 	} | ||||
| 
 | ||||
| 	return ret; | ||||
| }; | ||||
| 
 | ||||
| class LocConverter { | ||||
| 	/** | ||||
| 	 * @param {string} input input | ||||
|  | @ -482,7 +612,7 @@ class CssParser extends Parser { | |||
| 				// CSS Variable
 | ||||
| 				const { line: sl, column: sc } = locConverter.get(propertyNameStart); | ||||
| 				const { line: el, column: ec } = locConverter.get(propertyNameEnd); | ||||
| 				const name = propertyName.slice(2); | ||||
| 				const name = unescapeIdentifier(propertyName.slice(2)); | ||||
| 				const dep = new CssLocalIdentifierDependency( | ||||
| 					name, | ||||
| 					[propertyNameStart, propertyNameEnd], | ||||
|  | @ -490,9 +620,7 @@ class CssParser extends Parser { | |||
| 				); | ||||
| 				dep.setLoc(sl, sc, el, ec); | ||||
| 				module.addDependency(dep); | ||||
| 				declaredCssVariables.add( | ||||
| 					CssSelfLocalIdentifierDependency.unescapeIdentifier(name) | ||||
| 				); | ||||
| 				declaredCssVariables.add(name); | ||||
| 			} else if ( | ||||
| 				OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY.test(propertyName) | ||||
| 			) { | ||||
|  | @ -507,9 +635,11 @@ class CssParser extends Parser { | |||
| 			if (inAnimationProperty && lastIdentifier) { | ||||
| 				const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]); | ||||
| 				const { line: el, column: ec } = locConverter.get(lastIdentifier[1]); | ||||
| 				const name = lastIdentifier[2] | ||||
| 					? input.slice(lastIdentifier[0], lastIdentifier[1]) | ||||
| 					: input.slice(lastIdentifier[0] + 1, lastIdentifier[1] - 1); | ||||
| 				const name = unescapeIdentifier( | ||||
| 					lastIdentifier[2] | ||||
| 						? input.slice(lastIdentifier[0], lastIdentifier[1]) | ||||
| 						: input.slice(lastIdentifier[0] + 1, lastIdentifier[1] - 1) | ||||
| 				); | ||||
| 				const dep = new CssSelfLocalIdentifierDependency(name, [ | ||||
| 					lastIdentifier[0], | ||||
| 					lastIdentifier[1] | ||||
|  | @ -882,10 +1012,11 @@ class CssParser extends Parser { | |||
| 									end | ||||
| 								); | ||||
| 								if (!ident) return end; | ||||
| 								const name = | ||||
| 								const name = unescapeIdentifier( | ||||
| 									ident[2] === true | ||||
| 										? input.slice(ident[0], ident[1]) | ||||
| 										: input.slice(ident[0] + 1, ident[1] - 1); | ||||
| 										: input.slice(ident[0] + 1, ident[1] - 1) | ||||
| 								); | ||||
| 								const { line: sl, column: sc } = locConverter.get(ident[0]); | ||||
| 								const { line: el, column: ec } = locConverter.get(ident[1]); | ||||
| 								const dep = new CssLocalIdentifierDependency(name, [ | ||||
|  | @ -900,10 +1031,8 @@ class CssParser extends Parser { | |||
| 								if (!ident) return end; | ||||
| 								let name = input.slice(ident[0], ident[1]); | ||||
| 								if (!name.startsWith("--") || name.length < 3) return end; | ||||
| 								name = name.slice(2); | ||||
| 								declaredCssVariables.add( | ||||
| 									CssSelfLocalIdentifierDependency.unescapeIdentifier(name) | ||||
| 								); | ||||
| 								name = unescapeIdentifier(name.slice(2)); | ||||
| 								declaredCssVariables.add(name); | ||||
| 								const { line: sl, column: sc } = locConverter.get(ident[0]); | ||||
| 								const { line: el, column: ec } = locConverter.get(ident[1]); | ||||
| 								const dep = new CssLocalIdentifierDependency( | ||||
|  | @ -996,7 +1125,7 @@ class CssParser extends Parser { | |||
| 						end | ||||
| 					); | ||||
| 					if (!ident) return end; | ||||
| 					const name = input.slice(ident[0], ident[1]); | ||||
| 					const name = unescapeIdentifier(input.slice(ident[0], ident[1])); | ||||
| 					const dep = new CssLocalIdentifierDependency(name, [ | ||||
| 						ident[0], | ||||
| 						ident[1] | ||||
|  | @ -1013,7 +1142,7 @@ class CssParser extends Parser { | |||
| 			hash: (input, start, end, isID) => { | ||||
| 				if (isNextRulePrelude && isLocalMode() && isID) { | ||||
| 					const valueStart = start + 1; | ||||
| 					const name = input.slice(valueStart, end); | ||||
| 					const name = unescapeIdentifier(input.slice(valueStart, end)); | ||||
| 					const dep = new CssLocalIdentifierDependency(name, [valueStart, end]); | ||||
| 					const { line: sl, column: sc } = locConverter.get(start); | ||||
| 					const { line: el, column: ec } = locConverter.get(end); | ||||
|  | @ -1258,12 +1387,15 @@ class CssParser extends Parser { | |||
| 							if (name === "var") { | ||||
| 								const customIdent = walkCssTokens.eatIdentSequence(input, end); | ||||
| 								if (!customIdent) return end; | ||||
| 								const name = input.slice(customIdent[0], customIdent[1]); | ||||
| 								let name = input.slice(customIdent[0], customIdent[1]); | ||||
| 								// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.
 | ||||
| 								// The <custom-property-name> production corresponds to this:
 | ||||
| 								// it’s defined as any <dashed-ident> (a valid identifier that starts with two dashes),
 | ||||
| 								// except -- itself, which is reserved for future use by CSS.
 | ||||
| 								if (!name.startsWith("--") || name.length < 3) return end; | ||||
| 								name = unescapeIdentifier( | ||||
| 									input.slice(customIdent[0] + 2, customIdent[1]) | ||||
| 								); | ||||
| 								const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments( | ||||
| 									input, | ||||
| 									customIdent[1] | ||||
|  | @ -1301,7 +1433,7 @@ class CssParser extends Parser { | |||
| 									} else if (from[2] === false) { | ||||
| 										const dep = new CssIcssImportDependency( | ||||
| 											path.slice(1, -1), | ||||
| 											name.slice(2), | ||||
| 											name, | ||||
| 											[customIdent[0], from[1] - 1] | ||||
| 										); | ||||
| 										const { line: sl, column: sc } = locConverter.get( | ||||
|  | @ -1321,7 +1453,7 @@ class CssParser extends Parser { | |||
| 										customIdent[1] | ||||
| 									); | ||||
| 									const dep = new CssSelfLocalIdentifierDependency( | ||||
| 										name.slice(2), | ||||
| 										name, | ||||
| 										[customIdent[0], customIdent[1]], | ||||
| 										"--", | ||||
| 										declaredCssVariables | ||||
|  | @ -1466,3 +1598,5 @@ class CssParser extends Parser { | |||
| } | ||||
| 
 | ||||
| module.exports = CssParser; | ||||
| module.exports.escapeIdentifier = escapeIdentifier; | ||||
| module.exports.unescapeIdentifier = unescapeIdentifier; | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ const { cssExportConvention } = require("../util/conventions"); | |||
| const createHash = require("../util/createHash"); | ||||
| const { makePathsRelative } = require("../util/identifier"); | ||||
| const makeSerializable = require("../util/makeSerializable"); | ||||
| const memoize = require("../util/memoize"); | ||||
| const NullDependency = require("./NullDependency"); | ||||
| 
 | ||||
| /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ | ||||
|  | @ -30,6 +31,8 @@ const NullDependency = require("./NullDependency"); | |||
| /** @typedef {import("../util/Hash")} Hash */ | ||||
| /** @typedef {import("../util/createHash").Algorithm} Algorithm */ | ||||
| 
 | ||||
| const getCssParser = memoize(() => require("../css/CssParser")); | ||||
| 
 | ||||
| /** | ||||
|  * @param {string} local css local | ||||
|  * @param {CssModule} module module | ||||
|  | @ -78,50 +81,6 @@ const getLocalIdent = (local, module, chunkGraph, runtimeTemplate) => { | |||
| 		.replace(/^((-?[0-9])|--)/, "_$1"); | ||||
| }; | ||||
| 
 | ||||
| const CONTAINS_ESCAPE = /\\/; | ||||
| 
 | ||||
| /** | ||||
|  * @param {string} str string | ||||
|  * @returns {[string, number] | undefined} hex | ||||
|  */ | ||||
| const gobbleHex = str => { | ||||
| 	const lower = str.toLowerCase(); | ||||
| 	let hex = ""; | ||||
| 	let spaceTerminated = false; | ||||
| 
 | ||||
| 	for (let i = 0; i < 6 && lower[i] !== undefined; i++) { | ||||
| 		const code = lower.charCodeAt(i); | ||||
| 		// check to see if we are dealing with a valid hex char [a-f|0-9]
 | ||||
| 		const valid = (code >= 97 && code <= 102) || (code >= 48 && code <= 57); | ||||
| 		// https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
 | ||||
| 		spaceTerminated = code === 32; | ||||
| 		if (!valid) break; | ||||
| 		hex += lower[i]; | ||||
| 	} | ||||
| 
 | ||||
| 	if (hex.length === 0) return undefined; | ||||
| 
 | ||||
| 	const codePoint = Number.parseInt(hex, 16); | ||||
| 	const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff; | ||||
| 
 | ||||
| 	// Add special case for
 | ||||
| 	// "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
 | ||||
| 	// https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
 | ||||
| 	if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) { | ||||
| 		return ["\uFFFD", hex.length + (spaceTerminated ? 1 : 0)]; | ||||
| 	} | ||||
| 
 | ||||
| 	return [ | ||||
| 		String.fromCodePoint(codePoint), | ||||
| 		hex.length + (spaceTerminated ? 1 : 0) | ||||
| 	]; | ||||
| }; | ||||
| 
 | ||||
| // eslint-disable-next-line no-useless-escape
 | ||||
| const regexSingleEscape = /[ -,.\/:-@[\]\^`{-~]/; | ||||
| const regexExcessiveSpaces = | ||||
| 	/(^|\\+)?(\\[A-F0-9]{1,6})\u0020(?![a-fA-F0-9\u0020])/g; | ||||
| 
 | ||||
| class CssLocalIdentifierDependency extends NullDependency { | ||||
| 	/** | ||||
| 	 * @param {string} name name | ||||
|  | @ -130,99 +89,13 @@ class CssLocalIdentifierDependency extends NullDependency { | |||
| 	 */ | ||||
| 	constructor(name, range, prefix = "") { | ||||
| 		super(); | ||||
| 		this.name = CssLocalIdentifierDependency.unescapeIdentifier(name); | ||||
| 		this.name = name; | ||||
| 		this.range = range; | ||||
| 		this.prefix = prefix; | ||||
| 		this._conventionNames = undefined; | ||||
| 		this._hashUpdate = undefined; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @param {string} str string | ||||
| 	 * @returns {string} unescaped string | ||||
| 	 */ | ||||
| 	static unescapeIdentifier(str) { | ||||
| 		const needToProcess = CONTAINS_ESCAPE.test(str); | ||||
| 		if (!needToProcess) return str; | ||||
| 		let ret = ""; | ||||
| 		for (let i = 0; i < str.length; i++) { | ||||
| 			if (str[i] === "\\") { | ||||
| 				const gobbled = gobbleHex(str.slice(i + 1, i + 7)); | ||||
| 				if (gobbled !== undefined) { | ||||
| 					ret += gobbled[0]; | ||||
| 					i += gobbled[1]; | ||||
| 					continue; | ||||
| 				} | ||||
| 				// Retain a pair of \\ if double escaped `\\\\`
 | ||||
| 				// https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e
 | ||||
| 				if (str[i + 1] === "\\") { | ||||
| 					ret += "\\"; | ||||
| 					i += 1; | ||||
| 					continue; | ||||
| 				} | ||||
| 				// if \\ is at the end of the string retain it
 | ||||
| 				// https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb
 | ||||
| 				if (str.length === i + 1) { | ||||
| 					ret += str[i]; | ||||
| 				} | ||||
| 				continue; | ||||
| 			} | ||||
| 			ret += str[i]; | ||||
| 		} | ||||
| 
 | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @param {string} str string | ||||
| 	 * @returns {string} escaped identifier | ||||
| 	 */ | ||||
| 	static escapeIdentifier(str) { | ||||
| 		let output = ""; | ||||
| 		let counter = 0; | ||||
| 
 | ||||
| 		while (counter < str.length) { | ||||
| 			const character = str.charAt(counter++); | ||||
| 
 | ||||
| 			let value; | ||||
| 
 | ||||
| 			if (/[\t\n\f\r\u000B]/.test(character)) { | ||||
| 				const codePoint = character.charCodeAt(0); | ||||
| 
 | ||||
| 				value = `\\${codePoint.toString(16).toUpperCase()} `; | ||||
| 			} else if (character === "\\" || regexSingleEscape.test(character)) { | ||||
| 				value = `\\${character}`; | ||||
| 			} else { | ||||
| 				value = character; | ||||
| 			} | ||||
| 
 | ||||
| 			output += value; | ||||
| 		} | ||||
| 
 | ||||
| 		const firstChar = str.charAt(0); | ||||
| 
 | ||||
| 		if (/^-[-\d]/.test(output)) { | ||||
| 			output = `\\-${output.slice(1)}`; | ||||
| 		} else if (/\d/.test(firstChar)) { | ||||
| 			output = `\\3${firstChar} ${output.slice(1)}`; | ||||
| 		} | ||||
| 
 | ||||
| 		// Remove spaces after `\HEX` escapes that are not followed by a hex digit,
 | ||||
| 		// since they’re redundant. Note that this is only possible if the escape
 | ||||
| 		// sequence isn’t preceded by an odd number of backslashes.
 | ||||
| 		output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => { | ||||
| 			if ($1 && $1.length % 2) { | ||||
| 				// It’s not safe to remove the space, so don’t.
 | ||||
| 				return $0; | ||||
| 			} | ||||
| 
 | ||||
| 			// Strip the space.
 | ||||
| 			return ($1 || "") + $2; | ||||
| 		}); | ||||
| 
 | ||||
| 		return output; | ||||
| 	} | ||||
| 
 | ||||
| 	get type() { | ||||
| 		return "css local identifier"; | ||||
| 	} | ||||
|  | @ -325,7 +198,7 @@ CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTempla | |||
| 
 | ||||
| 		return ( | ||||
| 			dep.prefix + | ||||
| 			CssLocalIdentifierDependency.escapeIdentifier( | ||||
| 			getCssParser().escapeIdentifier( | ||||
| 				getLocalIdent(local, module, chunkGraph, runtimeTemplate) | ||||
| 			) | ||||
| 		); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue