@@ -13,13 +13,16 @@ const detectPageTechnologies = async (ruleConfig: Record<string, unknown> = {})
1313 const ruleRegexCache = new WeakMap ( )
1414 const ruleCombinedCache = new WeakMap ( )
1515 const ruleHintCache = new WeakMap ( )
16+ const lineProbeCache = new WeakMap ( )
1617 const resources = collectResources ( )
1718 const classTokens = collectClassTokens ( )
1819 const cssVariables = collectCssVariables ( )
1920 const documentHtmlSample = getHtmlSample ( )
2021 const globalKeys = safeGlobalKeys ( )
2122 const add = createCollector ( technologies )
2223 let cachedBodyText = null
24+ let classTokenSource = null
25+ let classTokenNames = null
2326 let sliceStartedAt = performance . now ( )
2427 const hintIndex = createHintIndex ( ruleConfig , {
2528 resources : resources . text ,
@@ -1152,10 +1155,8 @@ ${html}`
11521155
11531156 const matchResource = shouldMatchTarget ( rule , 'resources' )
11541157 const matchHtml = ! ruleResourceOnly && ! context . resourceOnly && shouldMatchTarget ( rule , 'html' )
1155- const allResources =
1156- Array . isArray ( rule . matchIn ) && rule . matchIn . includes ( 'url' )
1157- ? unique ( [ location . href , ...( context . resources ?. all || [ ] ) ] )
1158- : context . resources ?. all || [ ]
1158+ const includePageUrl = Array . isArray ( rule . matchIn ) && rule . matchIn . includes ( 'url' )
1159+ const allResources = getContextResources ( context , includePageUrl )
11591160 const htmlText = context . text || ''
11601161 const formatUrlEvidence = resource =>
11611162 resource === location . href ? `页面 URL 匹配 ${ shortUrl ( resource ) } ` : `资源 URL 匹配 ${ shortUrl ( resource ) } `
@@ -1187,10 +1188,7 @@ ${html}`
11871188 const combined = getCompiledCombinedPattern ( rule )
11881189 if ( combined ) {
11891190 if ( matchResource ) {
1190- const resource = allResources . find ( url => {
1191- combined . lastIndex = 0
1192- return combined . test ( url )
1193- } )
1191+ const resource = findMatchingResource ( combined , allResources , context , includePageUrl )
11941192 if ( resource ) {
11951193 return {
11961194 confidence : rule . confidence || context . resourceConfidence || '高' ,
@@ -1211,10 +1209,7 @@ ${html}`
12111209 const patterns = getCompiledRulePatterns ( rule )
12121210 for ( const pattern of patterns ) {
12131211 if ( matchResource ) {
1214- const resource = allResources . find ( url => {
1215- pattern . lastIndex = 0
1216- return pattern . test ( url )
1217- } )
1212+ const resource = findMatchingResource ( pattern , allResources , context , includePageUrl )
12181213 if ( resource ) {
12191214 return {
12201215 confidence : rule . confidence || context . resourceConfidence || '高' ,
@@ -1234,12 +1229,49 @@ ${html}`
12341229 return null
12351230 }
12361231
1232+ function getContextResources ( context , includePageUrl ) {
1233+ const key = includePageUrl ? '_resourcesWithPage' : '_resources'
1234+ if ( ! context [ key ] ) {
1235+ const list = context . resources ?. all || [ ]
1236+ context [ key ] = includePageUrl ? unique ( [ location . href , ...list ] ) : list
1237+ }
1238+ return context [ key ]
1239+ }
1240+
1241+ // 多行模式下 ^ / $ 按行匹配,与逐条 URL 测试等价;拼接只可能多出跨行命中,命中后仍逐条确认。
1242+ // 否定环视在行首行尾会把换行符当作相邻字符,可能少判,这类正则不做预判
1243+ function getLineProbe ( pattern ) {
1244+ let probe = lineProbeCache . get ( pattern )
1245+ if ( probe === undefined ) {
1246+ probe = / \( \? < ? ! / . test ( pattern . source ) ? null : new RegExp ( pattern . source , pattern . flags . replace ( / [ g y m ] / g, '' ) + 'm' )
1247+ lineProbeCache . set ( pattern , probe )
1248+ }
1249+ return probe
1250+ }
1251+
1252+ function findMatchingResource ( pattern , resources , context , includePageUrl ) {
1253+ if ( ! resources . length ) return undefined
1254+ const probe = getLineProbe ( pattern )
1255+ if ( probe ) {
1256+ const key = includePageUrl ? '_resourceLinesWithPage' : '_resourceLines'
1257+ if ( context [ key ] === undefined ) context [ key ] = resources . join ( '\n' )
1258+ if ( ! probe . test ( context [ key ] ) ) return undefined
1259+ }
1260+ return resources . find ( url => {
1261+ pattern . lastIndex = 0
1262+ return pattern . test ( url )
1263+ } )
1264+ }
1265+
12371266 function matchCssVariables ( rule , cssVariables ) {
12381267 if ( ! Array . isArray ( rule . cssVariables ) || ! rule . cssVariables . length || ! cssVariables ?. names ?. length ) {
12391268 return null
12401269 }
12411270
1242- const normalizedNames = new Set ( cssVariables . names . map ( name => name . toLowerCase ( ) ) )
1271+ if ( ! cssVariables . lowerNameSet ) {
1272+ cssVariables . lowerNameSet = new Set ( cssVariables . names . map ( name => name . toLowerCase ( ) ) )
1273+ }
1274+ const normalizedNames = cssVariables . lowerNameSet
12431275 const matched = rule . cssVariables . filter ( name => normalizedNames . has ( String ( name ) . toLowerCase ( ) ) )
12441276 const minMatches = Math . max ( 1 , Number ( rule . minCssVariableMatches || 1 ) )
12451277 if ( matched . length < minMatches ) {
@@ -1277,8 +1309,11 @@ ${html}`
12771309 const known = new Set ( )
12781310 const found = new Map ( )
12791311 const keyAt = ( text , index ) =>
1280- ( ( text . charCodeAt ( index ) * 65536 + text . charCodeAt ( index + 1 ) ) * 65536 + text . charCodeAt ( index + 2 ) ) * 65536 +
1281- text . charCodeAt ( index + 3 )
1312+ ( ( text . charCodeAt ( index ) << 21 ) ^
1313+ ( text . charCodeAt ( index + 1 ) << 14 ) ^
1314+ ( text . charCodeAt ( index + 2 ) << 7 ) ^
1315+ text . charCodeAt ( index + 3 ) ) &
1316+ 0x3fffffff
12821317 const register = hint => {
12831318 if ( hint . length < 4 || hint . includes ( '\n' ) || known . has ( hint ) ) return
12841319 known . add ( hint )
@@ -1655,7 +1690,11 @@ ${html}`
16551690 }
16561691
16571692 function hasClassPrefix ( classes , prefix ) {
1658- return Object . keys ( classes ) . some ( name => name . startsWith ( prefix ) )
1693+ if ( ! classTokenNames || classTokenSource !== classes ) {
1694+ classTokenSource = classes
1695+ classTokenNames = Object . keys ( classes )
1696+ }
1697+ return classTokenNames . some ( name => name . startsWith ( prefix ) )
16591698 }
16601699
16611700 function scoreTailwind ( classes ) {
0 commit comments