Skip to content

Commit e6b0fa3

Browse files
committed
fix(tailwindcss-patch): decouple runtime entry from cli bundle
1 parent 9695af5 commit e6b0fa3

File tree

6 files changed

+201
-1
lines changed

6 files changed

+201
-1
lines changed

.changeset/wise-lobsters-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tailwindcss-patch': patch
3+
---
4+
5+
Fix the published CommonJS runtime entry so non-CLI APIs no longer eagerly load the ESM-only `cac` dependency. CLI code is now split into a separate lazy-loaded bundle, which keeps `require('tailwindcss-patch')` working in CommonJS config loaders while preserving CLI factory support.

packages/tailwindcss-patch/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"description": "patch tailwindcss for exposing context and extract classes",
55
"author": "ice breaker <1324318532@qq.com>",
66
"license": "MIT",
7+
"engines": {
8+
"node": ">=18.0.0"
9+
},
710
"homepage": "https://mangle.icebreaker.top/",
811
"repository": {
912
"type": "git",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import process from 'node:process'
2+
import { createTailwindcssPatchCli, ValidateCommandError } from './index.bundle'
3+
import logger from './logger'
4+
5+
async function main() {
6+
const cli = createTailwindcssPatchCli()
7+
cli.help()
8+
cli.parse(process.argv, { run: false })
9+
await cli.runMatchedCommand()
10+
}
11+
12+
main().catch((error) => {
13+
if (error instanceof ValidateCommandError) {
14+
process.exitCode = error.exitCode
15+
return
16+
}
17+
const message = error instanceof Error ? error.message : String(error)
18+
logger.error(message)
19+
process.exitCode = 1
20+
})
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type { TailwindcssMangleConfig } from '@tailwindcss-mangle/config'
2+
import type { CAC } from 'cac'
3+
import type { TailwindcssPatchCliMountOptions, TailwindcssPatchCliOptions } from './commands/types'
4+
5+
import { createRequire } from 'node:module'
6+
import { TailwindcssPatcher } from './api/tailwindcss-patcher'
7+
import { CacheStore } from './cache/store'
8+
import {
9+
migrateConfigFiles,
10+
MIGRATION_REPORT_KIND,
11+
MIGRATION_REPORT_SCHEMA_VERSION,
12+
restoreConfigFiles,
13+
} from './commands/migrate-config'
14+
import { tailwindcssPatchCommands } from './commands/types'
15+
import {
16+
VALIDATE_EXIT_CODES,
17+
VALIDATE_FAILURE_REASONS,
18+
ValidateCommandError,
19+
} from './commands/validate'
20+
import { normalizeOptions } from './config'
21+
import {
22+
extractProjectCandidatesWithPositions,
23+
extractRawCandidates,
24+
extractRawCandidatesWithPositions,
25+
extractValidCandidates,
26+
groupTokensByFile,
27+
} from './extraction/candidate-extractor'
28+
import {
29+
collectClassesFromContexts,
30+
collectClassesFromTailwindV4,
31+
getPatchStatusReport,
32+
loadRuntimeContexts,
33+
runTailwindBuild,
34+
} from './install'
35+
import logger from './logger'
36+
37+
const require = createRequire(import.meta.url)
38+
39+
type CliModule = typeof import('./commands/cli')
40+
41+
function loadCliModule(): CliModule {
42+
return require('./commands/cli-runtime.js') as CliModule
43+
}
44+
45+
export {
46+
CacheStore,
47+
collectClassesFromContexts,
48+
collectClassesFromTailwindV4,
49+
extractProjectCandidatesWithPositions,
50+
extractRawCandidates,
51+
extractRawCandidatesWithPositions,
52+
extractValidCandidates,
53+
getPatchStatusReport,
54+
groupTokensByFile,
55+
loadRuntimeContexts,
56+
logger,
57+
migrateConfigFiles,
58+
MIGRATION_REPORT_KIND,
59+
MIGRATION_REPORT_SCHEMA_VERSION,
60+
normalizeOptions,
61+
restoreConfigFiles,
62+
runTailwindBuild,
63+
tailwindcssPatchCommands,
64+
TailwindcssPatcher,
65+
VALIDATE_EXIT_CODES,
66+
VALIDATE_FAILURE_REASONS,
67+
ValidateCommandError,
68+
}
69+
export type {
70+
ConfigFileMigrationEntry,
71+
ConfigFileMigrationReport,
72+
MigrateConfigFilesOptions,
73+
RestoreConfigFilesOptions,
74+
RestoreConfigFilesResult,
75+
} from './commands/migrate-config'
76+
export type {
77+
TailwindcssPatchCliMountOptions,
78+
TailwindcssPatchCliOptions,
79+
TailwindcssPatchCommand,
80+
TailwindcssPatchCommandContext,
81+
TailwindcssPatchCommandHandler,
82+
TailwindcssPatchCommandHandlerMap,
83+
TailwindcssPatchCommandOptionDefinition,
84+
TailwindcssPatchCommandOptions,
85+
} from './commands/types'
86+
export type {
87+
ValidateFailureReason,
88+
ValidateFailureSummary,
89+
ValidateJsonFailurePayload,
90+
ValidateJsonSuccessPayload,
91+
} from './commands/validate'
92+
export type { TailwindCssPatchOptions } from './config'
93+
export * from './types'
94+
95+
export function mountTailwindcssPatchCommands(cli: CAC, options: TailwindcssPatchCliMountOptions = {}) {
96+
return loadCliModule().mountTailwindcssPatchCommands(cli, options)
97+
}
98+
99+
export function createTailwindcssPatchCli(options: TailwindcssPatchCliOptions = {}) {
100+
return loadCliModule().createTailwindcssPatchCli(options)
101+
}
102+
103+
export function defineConfig<T extends TailwindcssMangleConfig>(config: T): T {
104+
return config
105+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { execFileSync } from 'node:child_process'
2+
import path from 'pathe'
3+
import { describe, expect, it } from 'vitest'
4+
5+
const packageDir = path.resolve(__dirname, '..')
6+
const distEntry = path.join(packageDir, 'dist/index.js')
7+
8+
function runCommonJs(script: string) {
9+
return execFileSync(process.execPath, ['-e', script], {
10+
cwd: packageDir,
11+
encoding: 'utf8',
12+
}).trim()
13+
}
14+
15+
describe('published CommonJS runtime entry', () => {
16+
it('requires non-CLI runtime APIs without triggering ERR_REQUIRE_ESM', () => {
17+
const output = runCommonJs(`
18+
;(async () => {
19+
const patch = require(${JSON.stringify(distEntry)})
20+
const normalized = patch.normalizeOptions({ cache: false })
21+
const extracted = await patch.extractRawCandidatesWithPositions('<div class="text-red-500"></div>', 'html')
22+
console.log(JSON.stringify({
23+
normalizedCacheEnabled: normalized.cache.enabled,
24+
containsUtilityCandidate: extracted.some(entry => entry.rawCandidate === 'text-red-500'),
25+
hasPatcher: typeof patch.TailwindcssPatcher,
26+
hasExtractValidCandidates: typeof patch.extractValidCandidates,
27+
}))
28+
})().catch((error) => {
29+
console.error(error)
30+
process.exit(1)
31+
})
32+
`)
33+
34+
expect(JSON.parse(output)).toEqual({
35+
normalizedCacheEnabled: false,
36+
containsUtilityCandidate: true,
37+
hasPatcher: 'function',
38+
hasExtractValidCandidates: 'function',
39+
})
40+
})
41+
42+
it('creates the CLI on demand from the CommonJS entry', () => {
43+
const output = runCommonJs(`
44+
const patch = require(${JSON.stringify(distEntry)})
45+
const cli = patch.createTailwindcssPatchCli({
46+
name: 'embedded',
47+
mountOptions: {
48+
commands: ['status'],
49+
},
50+
})
51+
console.log(JSON.stringify({
52+
name: cli.name,
53+
commands: cli.commands.map(command => command.name),
54+
}))
55+
`)
56+
57+
expect(JSON.parse(output)).toEqual({
58+
name: 'embedded',
59+
commands: ['status'],
60+
})
61+
})
62+
})
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { defineConfig } from 'tsup'
22

33
export default defineConfig({
4-
entry: ['src/index.ts', 'src/cli.ts'],
4+
entry: {
5+
'index': 'src/index.bundle.ts',
6+
'cli': 'src/cli.bundle.ts',
7+
'commands/cli-runtime': 'src/commands/cli.ts',
8+
},
59
shims: true,
610
clean: true,
711
format: ['cjs', 'esm'], // , 'esm'
812
dts: true,
913
cjsInterop: true,
1014
splitting: true,
15+
noExternal: ['cac'],
1116
external: ['tailwindcss', '@tailwindcss/node', '@tailwindcss/oxide'],
1217
})

0 commit comments

Comments
 (0)