Skip to content

Commit 6716921

Browse files
committed
chore: commit monkeyPatchForSupportingCustomUnitV4
1 parent 41dc914 commit 6716921

File tree

15 files changed

+3575
-448
lines changed

15 files changed

+3575
-448
lines changed

.changeset/strong-hounds-fall.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tailwindcss-patch": major
3+
---
4+
5+
feat: add v4 SupportingCustomUnit patch

package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"type": "module",
44
"version": "4.0.0",
55
"private": true,
6-
"packageManager": "pnpm@10.3.0",
6+
"packageManager": "pnpm@10.5.2",
77
"engines": {
88
"node": ">=18.0.0"
99
},
@@ -54,15 +54,15 @@
5454
"@types/html-minifier-terser": "^7.0.2",
5555
"@types/klaw": "^3.0.6",
5656
"@types/lint-staged": "^13.3.0",
57-
"@types/lodash": "^4.17.15",
57+
"@types/lodash": "^4.17.16",
5858
"@types/lodash-es": "^4.17.12",
5959
"@types/micromatch": "^4.0.9",
60-
"@types/node": "^22.13.5",
60+
"@types/node": "^22.13.8",
6161
"@types/resolve": "^1.20.6",
6262
"@types/semver": "^7.5.8",
6363
"@types/set-value": "^4.0.3",
6464
"@vitest/coverage-v8": "~3.0.7",
65-
"astro": "^5.4.0",
65+
"astro": "^5.4.1",
6666
"ci-info": "^4.1.0",
6767
"comment-json": "^4.2.5",
6868
"cross-env": "^7.0.3",
@@ -88,13 +88,13 @@
8888
"normalize-newline": "^4.1.0",
8989
"only-allow": "^1.2.1",
9090
"pathe": "^2.0.3",
91-
"pkg-types": "^2.0.0",
91+
"pkg-types": "^2.0.1",
9292
"postcss": "^8.5.3",
9393
"postcss-loader": "^8.1.1",
9494
"postcss7": "npm:postcss@7",
9595
"prettier": "^3.5.2",
9696
"rimraf": "^6.0.1",
97-
"rollup": "^4.34.8",
97+
"rollup": "^4.34.9",
9898
"set-value": "^4.1.0",
9999
"tailwindcss": "^3.4.17",
100100
"tailwindcss-patch": "workspace:*",
@@ -103,7 +103,7 @@
103103
"tsup": "^8.4.0",
104104
"tsx": "^4.19.3",
105105
"turbo": "^2.4.4",
106-
"typescript": "^5.7.3",
106+
"typescript": "^5.8.2",
107107
"unbuild": "^3.5.0",
108108
"unplugin": "^2.2.0",
109109
"unplugin-tailwindcss-mangle": "workspace:*",

packages/tailwindcss-patch/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"@tailwindcss/oxide": "^4.0.9",
9191
"@tailwindcss/postcss": "^4.0.9",
9292
"@tailwindcss/vite": "^4.0.9",
93-
"tailwindcss": "^4.0.6",
93+
"tailwindcss": "^4.0.9",
9494
"tailwindcss-3": "npm:tailwindcss@^3",
9595
"tailwindcss-4": "npm:tailwindcss@^4.0.9"
9696
}

packages/tailwindcss-patch/src/core/patcher.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { InternalCacheOptions, InternalPatchOptions, PackageInfo, TailwindcssClassCache, TailwindcssPatcherOptions, TailwindcssRuntimeContext } from '../types'
1+
import type { PackageInfo } from 'local-pkg'
2+
import type { InternalCacheOptions, InternalPatchOptions, TailwindcssClassCache, TailwindcssPatcherOptions, TailwindcssRuntimeContext } from '../types'
23
import { createRequire } from 'node:module'
34
import process from 'node:process'
45
import fs from 'fs-extra'
@@ -50,15 +51,13 @@ export class TailwindcssPatcher {
5051
if (this.patchOptions.tailwindcss?.version) {
5152
this.majorVersion = this.patchOptions.tailwindcss.version
5253
}
53-
this.packageInfo = packageInfo
54+
this.packageInfo = packageInfo as PackageInfo
5455
this.patch = () => {
55-
if (this.majorVersion === 3 || this.majorVersion === 2) {
56-
try {
57-
return internalPatch(this.packageInfo?.packageJsonPath, this.patchOptions)
58-
}
59-
catch (error) {
60-
logger.error(`patch tailwindcss failed: ${(<Error>error).message}`)
61-
}
56+
try {
57+
return internalPatch(this.packageInfo, this.patchOptions)
58+
}
59+
catch (error) {
60+
logger.error(`Patch Tailwind CSS Failed: ${(<Error>error).message}`)
6261
}
6362
}
6463
}

packages/tailwindcss-patch/src/core/patches/supportCustomUnits/index.ts

+77-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import fs from 'fs-extra'
66
import path from 'pathe'
77
import { generate, parse, traverse } from '../../../babel'
88
import logger from '../../../logger'
9+
import { spliceChangesIntoString } from '../../../utils'
910

1011
function findAstNode(content: string, options: ILengthUnitsPatchOptions) {
1112
const { variableName, units } = options
@@ -22,14 +23,13 @@ function findAstNode(content: string, options: ILengthUnitsPatchOptions) {
2223
) {
2324
arrayRef = path.parent.init
2425
const set = new Set(path.parent.init.elements.map(x => (<StringLiteral>x).value))
25-
for (let i = 0; i < units.length; i++) {
26-
const unit = units[i]
26+
for (const unit of units) {
2727
if (!set.has(unit)) {
2828
path.parent.init.elements = path.parent.init.elements.map((x) => {
2929
if (t.isStringLiteral(x)) {
3030
return {
31-
type: x?.type,
32-
value: x?.value,
31+
type: x.type,
32+
value: x.value,
3333
}
3434
}
3535
return x
@@ -50,7 +50,7 @@ function findAstNode(content: string, options: ILengthUnitsPatchOptions) {
5050
}
5151
}
5252

53-
export function monkeyPatchForSupportingCustomUnit(rootDir: string, options?: Partial<ILengthUnitsPatchOptions>) {
53+
export function monkeyPatchForSupportingCustomUnitV3(rootDir: string, options?: Partial<ILengthUnitsPatchOptions>) {
5454
const opts = defuOverrideArray<Required<ILengthUnitsPatchOptions>, ILengthUnitsPatchOptions[]>(options as Required<ILengthUnitsPatchOptions>, {
5555
units: ['rpx'],
5656
lengthUnitsFilePath: 'lib/util/dataTypes.js',
@@ -86,3 +86,75 @@ export function monkeyPatchForSupportingCustomUnit(rootDir: string, options?: Pa
8686
}
8787
}
8888
}
89+
90+
// "cm","mm","Q","in","pc","pt","px","em","ex","ch","rem","lh","rlh","vw","vh","vmin","vmax","vb","vi","svw","svh","lvw","lvh","dvw","dvh","cqw","cqh","cqi","cqb","cqmin","cqmax"
91+
92+
export function monkeyPatchForSupportingCustomUnitV4(rootDir: string, options?: Partial<ILengthUnitsPatchOptions>) {
93+
const opts = defuOverrideArray<Required<ILengthUnitsPatchOptions>, ILengthUnitsPatchOptions[]>(options as Required<ILengthUnitsPatchOptions>, {
94+
units: ['rpx'],
95+
overwrite: true,
96+
})
97+
const distPath = path.resolve(rootDir, 'dist')
98+
const list = fs.readdirSync(distPath)
99+
const chunks = list.filter(x => x.startsWith('chunk-'))
100+
const guessUnitStart = /\[\s*["']cm["'],\s*["']mm["'],[\w,"]+\]/
101+
let code
102+
let matches: RegExpMatchArray | null = null
103+
let guessFile: string | undefined
104+
for (const chunkName of chunks) {
105+
guessFile = path.join(distPath, chunkName)
106+
code = fs.readFileSync(guessFile, 'utf8')
107+
const res = guessUnitStart.exec(code)
108+
if (res) {
109+
matches = res
110+
break
111+
}
112+
}
113+
let hasPatched = false
114+
if (matches && code) {
115+
const match = matches[0]
116+
const ast = parse(match, {
117+
sourceType: 'unambiguous',
118+
})
119+
120+
traverse(ast, {
121+
ArrayExpression(path) {
122+
for (const unit of opts.units) {
123+
if (path.node.elements.some(x => t.isStringLiteral(x) && x.value === unit)) {
124+
hasPatched = true
125+
break
126+
}
127+
path.node.elements.push(t.stringLiteral(unit))
128+
}
129+
},
130+
})
131+
if (hasPatched) {
132+
return {
133+
code,
134+
hasPatched,
135+
}
136+
}
137+
const { code: replacement } = generate(ast, {
138+
minified: true,
139+
})
140+
code = spliceChangesIntoString(code, [
141+
{
142+
start: matches.index as number,
143+
end: matches.index as number + match.length,
144+
replacement: replacement.endsWith(';') ? replacement.slice(0, -1) : replacement,
145+
},
146+
])
147+
if (opts.overwrite && guessFile) {
148+
fs.writeFileSync(guessFile, code, {
149+
encoding: 'utf8',
150+
})
151+
logger.success('patch tailwindcss for custom length unit successfully!')
152+
}
153+
}
154+
155+
return {
156+
code,
157+
hasPatched,
158+
}
159+
// /\["cm","mm"/
160+
}

packages/tailwindcss-patch/src/core/runtime.ts

+33-7
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,51 @@
1+
import type { PackageInfo } from 'local-pkg'
12
import type { PackageJson } from 'pkg-types'
23
import type { ILengthUnitsPatchOptions, InternalPatchOptions } from '../types'
34
import { createRequire } from 'node:module'
45
import { defu } from '@tailwindcss-mangle/shared'
56
import path from 'pathe'
67
import { gte } from 'semver'
7-
import { monkeyPatchForExposingContextV2, monkeyPatchForExposingContextV3, monkeyPatchForSupportingCustomUnit } from './patches'
8+
import { monkeyPatchForExposingContextV2, monkeyPatchForExposingContextV3, monkeyPatchForSupportingCustomUnitV3, monkeyPatchForSupportingCustomUnitV4 } from './patches'
89

910
const require = createRequire(import.meta.url)
10-
export function internalPatch(pkgJsonPath: string | undefined, options: InternalPatchOptions) {
11+
12+
export function internalPatch(pkgJsonPath: PackageInfo, options: InternalPatchOptions): any
13+
export function internalPatch(pkgJsonPath: string, options: InternalPatchOptions): any
14+
export function internalPatch(pkgJsonPath: PackageInfo | string, options: InternalPatchOptions) {
1115
if (pkgJsonPath) {
12-
const pkgJson = require(pkgJsonPath) as PackageJson
13-
const twDir = path.dirname(pkgJsonPath)
14-
options.version = pkgJson.version
15-
if (gte(pkgJson.version!, '3.0.0')) {
16+
let pkgJson: PackageJson
17+
let twDir: string
18+
if (typeof pkgJsonPath === 'string') {
19+
pkgJson = require(pkgJsonPath) as PackageJson
20+
twDir = path.dirname(pkgJsonPath)
21+
options.version = pkgJson.version
22+
}
23+
else if (typeof pkgJsonPath === 'object') {
24+
pkgJson = pkgJsonPath.packageJson
25+
twDir = pkgJsonPath.rootPath
26+
options.version = pkgJsonPath.version
27+
}
28+
else {
29+
throw new TypeError('tailwindcss not found')
30+
}
31+
if (gte(pkgJson.version!, '4.0.0')) {
32+
try {
33+
if (options.applyPatches?.extendLengthUnits) {
34+
return monkeyPatchForSupportingCustomUnitV4(twDir, options)
35+
}
36+
}
37+
catch {
38+
39+
}
40+
}
41+
else if (gte(pkgJson.version!, '3.0.0')) {
1642
let result: Record<string, any> | undefined = {}
1743
if (options.applyPatches?.exportContext) {
1844
result = monkeyPatchForExposingContextV3(twDir, options)
1945
}
2046
if (options.applyPatches?.extendLengthUnits) {
2147
try {
22-
Object.assign(result ?? {}, monkeyPatchForSupportingCustomUnit(twDir, defu<Partial<ILengthUnitsPatchOptions>, Partial<ILengthUnitsPatchOptions>[]>(options.applyPatches.extendLengthUnits === true ? undefined : options.applyPatches.extendLengthUnits, {
48+
Object.assign(result ?? {}, monkeyPatchForSupportingCustomUnitV3(twDir, defu<Partial<ILengthUnitsPatchOptions>, Partial<ILengthUnitsPatchOptions>[]>(options.applyPatches.extendLengthUnits === true ? undefined : options.applyPatches.extendLengthUnits, {
2349
overwrite: options.overwrite,
2450
})))
2551
}

packages/tailwindcss-patch/src/types.ts

-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable ts/no-unsafe-function-type */
22
import type { PatchUserConfig, TailwindcssUserConfig } from '@tailwindcss-mangle/config'
3-
import type { PackageJson } from 'pkg-types'
43
import type { Node, Rule } from 'postcss'
54
import type { Config } from 'tailwindcss'
65

@@ -10,14 +9,6 @@ export type {
109
TailwindcssUserConfig,
1110
}
1211

13-
export interface PackageInfo {
14-
name: string
15-
version: string | undefined
16-
rootPath: string
17-
packageJsonPath: string
18-
packageJson: PackageJson
19-
}
20-
2112
export interface CacheOptions {
2213
dir?: string
2314
cwd?: string
+45
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,48 @@
11
export function isObject(val: any) {
22
return val !== null && typeof val === 'object' && Array.isArray(val) === false
33
};
4+
5+
export interface StringChange {
6+
start: number
7+
end: number
8+
replacement: string
9+
}
10+
11+
/**
12+
* Apply the changes to the string such that a change in the length
13+
* of the string does not break the indexes of the subsequent changes.
14+
*/
15+
export function spliceChangesIntoString(str: string, changes: StringChange[]) {
16+
// If there are no changes, return the original string
17+
if (!changes[0]) {
18+
return str
19+
}
20+
21+
// Sort all changes in order to make it easier to apply them
22+
changes.sort((a, b) => {
23+
return a.end - b.end || a.start - b.start
24+
})
25+
26+
// Append original string between each chunk, and then the chunk itself
27+
// This is sort of a String Builder pattern, thus creating less memory pressure
28+
let result = ''
29+
30+
let previous = changes[0]
31+
32+
result += str.slice(0, previous.start)
33+
result += previous.replacement
34+
35+
for (let i = 1; i < changes.length; ++i) {
36+
const change = changes[i]
37+
38+
result += str.slice(previous.end, change.start)
39+
result += change.replacement
40+
41+
previous = change
42+
}
43+
44+
// Add leftover string from last chunk to end
45+
result += str.slice(previous.end)
46+
47+
return result
48+
}

0 commit comments

Comments
 (0)