tuist / SwiftyTailwind

💇 A Swift Package to pull and run Tailwind from a Swift process
https://swiftytailwind.tuist.io
MIT License
59 stars 7 forks source link

SwiftyTailwind 🍃

All Contributors

SwiftyTailwind

SwiftyTailwind is a Swift Package to lazily download and run the Tailwind CLI from a Swift project (e.g. Vapor app or Publish project).

Usage

First, you need to add SwiftyTailwind as a dependency in your project's Package.swift:

.package(url: "https://github.com/tuist/SwiftyTailwind.git", .upToNextMinor(from: "0.5.0"))

Once added, you'll create an instance of SwiftyTailwind specifying the version you'd like to use and where you'd like it to be downloaded.

let tailwind = SwiftyTailwind(version: .latest, directory: "./cache")

If you don't pass any argument, it defaults to the latest version in the system's default temporary directory. If you work in a team, we recommend fixing the version to minimize non-determinism across environments.

Initializing a tailwind.config.js

You can create a tailwind.config.js configuration file by running the initialize function on the SwiftyTailwind instance:

try await tailwind.initialize()

Check out all the available options in the documentation.

Running Tailwind

To run Tailwind against a project, you can use the run function:

try await subject.run(input: inputCSSPath, output: outputCSSPath, options: .content("views/**/*.html"))

If you'd like Tailwind to keep watching for file changes, you can pass the .watch option:

try await subject.run(input: inputCSSPath, 
                      output: outputCSSPath, 
                      options: .watch, .content("views/**/*.html"))

Check out all the available options in the documentation.

Integrating with Vapor

You can integrate this with Vapor by setting up a tailwind.swift:

import SwiftyTailwind
import TSCBasic
import Vapor

func tailwind(_ app: Application) async throws {
  let resourcesDirectory = try AbsolutePath(validating: app.directory.resourcesDirectory)
  let publicDirectory = try AbsolutePath(validating: app.directory.publicDirectory)

  let tailwind = SwiftyTailwind()
  try await tailwind.run(
    input: .init(validating: "Styles/app.css", relativeTo: resourcesDirectory),
    output: .init(validating: "styles/app.generated.css", relativeTo: publicDirectory),
    options: .content("\(app.directory.viewsDirectory)**/*.leaf")
  )
}

Then in configure.swift:

try await tailwind(app)
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))

And in your index.leaf:

<link rel="stylesheet" href="https://github.com/tuist/SwiftyTailwind/blob/main/styles/app.generated.css" />

Running Vapor and Tailwind watch in parallel

It can be desirable for Tailwind to watch and rebuild changes without restarting the Vapor server. It is also best to restrict this behavior for development only.

You can integrate this behavior by setting up a tailwind.swift:

#if DEBUG
import SwiftyTailwind
import TSCBasic
import Vapor

func runTailwind(_ app: Application) async throws {
    let resourcesDirectory = try AbsolutePath(validating: app.directory.resourcesDirectory)
    let publicDirectory = try AbsolutePath(validating: app.directory.publicDirectory)
    let tailwind = SwiftyTailwind()

    async let runTailwind: () = tailwind.run(
        input: .init(validating: "Styles/app.css", relativeTo: resourcesDirectory),
        output: .init(validating: "styles/app.generated.css", relativeTo: publicDirectory),
        options: .watch, .content("\(app.directory.viewsDirectory)**/*.leaf"))
    return try await runTailwind
}
#endif

and then in entrypoint.swift, replace try await app.execute() with:

#if DEBUG
        if (env.arguments.contains { arg in arg == "migrate" }) {
            try await app.execute()
        } else {
            async let runApp: () = try await app.execute()
            _ = await [try runTailwind(app), try await runApp]
        }
#else
        try await app.execute()
#endif

The check for migrate in the arguments will ensure that it doesn't run when doing migrations in development. Additionally, it may be a good idea to setup a script to minify the CSS before deploying to production.

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Chris
Chris

🖋
William Sedlacek
William Sedlacek

📖
Brady Klein
Brady Klein

💻

This project follows the all-contributors specification. Contributions of any kind welcome!