Improve source file detection and tests

- Use SwiftPM-provided sourceFiles for filtering - Add Utils.swift with
URL descendant check - Update example to use Views/ instead of Folder/ -
Add test for non-included Swift file - Update .zed settings to disable
VSCode CSS language server
This commit is contained in:
Shibo Lyu 2025-08-20 21:01:17 +08:00
parent cfb5d2a4a2
commit 8359d795eb
8 changed files with 68 additions and 9 deletions

View file

@ -48,6 +48,10 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin {
context: PluginContext,
target: Target
) throws -> [Command] {
guard let sourceFileURLs = target.sourceModule?.sourceFiles.map({ $0.url }) else {
throw BuildError.notASourceModule
}
let tailwindCSSURL: URL = target.directoryURL.appending(component: "Tailwind.css")
guard let cssContent = try? String(contentsOf: tailwindCSSURL) else {
throw BuildError.missingTailwindCSSFile
@ -60,18 +64,25 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin {
throw BuildError.sourceNotDeclarationUnsupported
}
let sourcePaths =
let sourcePatterns =
cssContent
.matches(of: sourceDeclarationRegex)
.compactMap { String($0.output.1) }
let sourceURLs: [URL] = sourcePaths.map { path in
// Simplified handling: If ** is used, we just include everything in the directory.
let sourcePatternURLs: [URL] = sourcePatterns.map { path in
// Simplified handling: If `**` is used, we just include everything in the directory.
// It's unlikely we will have the same glob processing logic as Tailwind CSS CLI,
// so we may as well just expand the coverage.
// This only affects SwiftPM change detection: Tailwind CSS CLI will handle the globbing correctly.
let globlessPath = path.replacing(/\*\*.*/, with: "")
return target.directoryURL
.appending(component: globlessPath, directoryHint: .inferFromPath)
.resolvingSymlinksInPath()
}
let includedSourceURLs = sourceFileURLs.filter { file in
sourcePatternURLs.contains { file.isOrIsDescendant(of: $0) }
}
let tailwindCLIURL: URL = try context.tool(named: "tailwindcss").url
let outputBundleURL = context.pluginWorkDirectoryURL
.appending(component: outputBundleName, directoryHint: .isDirectory)
@ -79,9 +90,10 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin {
component: outputCSSFilename, directoryHint: .notDirectory)
print("Tailwind CSS Build Plugin")
print("Tailwind CSS File: \(tailwindCSSURL.path)")
print("@source declarations: \(sourcePaths)")
print("Source files: \(sourceURLs.map(\.path))")
print("Tailwind.css: \(tailwindCSSURL.path)")
print("@source declarations: \(sourcePatterns)")
print("All source files: \(sourceFileURLs.map(\.path))")
print("Input files: \(includedSourceURLs.map(\.path))")
print("Output: \(outputURL.path)")
return [
@ -93,7 +105,7 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin {
"--output", outputURL.path,
"--minify",
],
inputFiles: [tailwindCSSURL] + sourceURLs,
inputFiles: [tailwindCSSURL] + includedSourceURLs,
outputFiles: [outputBundleURL]
)
]
@ -102,12 +114,15 @@ struct TailwindCSSBuildPlugin: BuildToolPlugin {
extension TailwindCSSBuildPlugin {
enum BuildError: Error {
case notASourceModule
case missingTailwindCSSFile
case missingImportStatement
case sourceNotDeclarationUnsupported
var localizedDescription: String {
switch self {
case .notASourceModule:
"The target is not a source module."
case .missingTailwindCSSFile:
"Tailwind.css file not found in the target directory."
case .missingImportStatement:

View file

@ -0,0 +1,24 @@
import Foundation
extension URL {
func isOrIsDescendant(of ancestor: URL) -> Bool {
guard ancestor.isFileURL, self.isFileURL else {
return false
}
let ancestorComponents = ancestor.pathComponents
let selfComponents = self.pathComponents
guard selfComponents.count >= ancestorComponents.count else {
return false
}
for (index, component) in ancestorComponents.enumerated() {
if selfComponents[index] != component {
return false
}
}
return true
}
}