Skip to content

feat: fall back to locally-installed tailwindcss executables #226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 30, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
## next / unreleased

* Update to [Tailwind CSS v3.3.0](https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.3.0) by @tysongach
* Users can use a locally-installed `tailwindcss` executable by setting a `TAILWINDCSS_INSTALL_DIR` environment variable. (#224, #226) by @flavorjones


## v2.0.25 / 2023-03-14
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -9,7 +9,34 @@ With Rails 7 you can generate a new application preconfigured with Tailwind by u
1. Run `./bin/bundle add tailwindcss-rails`
2. Run `./bin/rails tailwindcss:install`

This gem wraps [the standalone executable version](https://tailwindcss.com/blog/standalone-cli) of the Tailwind CSS v3 framework. These executables are platform specific, so there are actually separate underlying gems per platform, but the correct gem will automatically be picked for your platform. Supported platforms are Linux x64, macOS arm64, macOS x64, and Windows x64. (Note that due to this setup, you must install the actual gems – you can't pin your gem to the github repo.)
This gem wraps [the standalone executable version](https://tailwindcss.com/blog/standalone-cli) of the Tailwind CSS v3 framework. These executables are platform specific, so there are actually separate underlying gems per platform, but the correct gem will automatically be picked for your platform.

Supported platforms are:

- arm64-darwin (macos-arm64)
- x64-mingw32 (windows-x64)
- x64-mingw-ucr (windows-x64)
- x86_64-darwin (macos-x64)
- x86_64-linux (linux-x64)
- aarch64-linux (linux-arm64)
- arm-linux (linux-armv7)


### Using a local installation of `tailwindcss`

If you are not able to use the vendored standalone executables (for example, if you're on an unsupported platform), you can use a local installation of the `tailwindcss` executable by setting an environment variable named `TAILWINDCSS_INSTALL_DIR` to the directory containing the executable.

For example, if you've installed `tailwindcss` so that the executable is found at `/node_modules/bin/tailwindcss`, then you should set your environment variable like so:

``` sh
TAILWINDCSS_INSTALL_DIR=/path/to/node_modules/bin
```

This also works with relative paths. If you've installed into your app's directory at `./node_modules/.bin/tailwindcss`:

``` sh
TAILWINDCSS_INSTALL_DIR=node_modules/.bin
```


## Developing with Tailwindcss
45 changes: 31 additions & 14 deletions lib/tailwindcss/commands.rb
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

module Tailwindcss
module Commands
DEFAULT_DIR = File.expand_path(File.join(__dir__, "..", "..", "exe"))

# raised when the host platform is not supported by upstream tailwindcss's binary releases
class UnsupportedPlatformException < StandardError
end
@@ -10,26 +12,41 @@ class UnsupportedPlatformException < StandardError
class ExecutableNotFoundException < StandardError
end

# raised when TAILWINDCSS_INSTALL_DIR does not exist
class DirectoryNotFoundException < StandardError
end

class << self
def platform
[:cpu, :os].map { |m| Gem::Platform.local.send(m) }.join("-")
end

def executable(
exe_path: File.expand_path(File.join(__dir__, "..", "..", "exe"))
)
if Tailwindcss::Upstream::NATIVE_PLATFORMS.keys.none? { |p| Gem::Platform.match(Gem::Platform.new(p)) }
raise UnsupportedPlatformException, <<~MESSAGE
tailwindcss-rails does not support the #{platform} platform
Please install tailwindcss following instructions at https://tailwindcss.com/docs/installation
MESSAGE
end

exe_path = Dir.glob(File.expand_path(File.join(exe_path, "*", "tailwindcss"))).find do |f|
Gem::Platform.match(Gem::Platform.new(File.basename(File.dirname(f))))
def executable(exe_path: DEFAULT_DIR)
tailwindcss_install_dir = ENV["TAILWINDCSS_INSTALL_DIR"]
if tailwindcss_install_dir
if File.directory?(tailwindcss_install_dir)
warn "NOTE: using TAILWINDCSS_INSTALL_DIR to find tailwindcss executable: #{tailwindcss_install_dir}"
exe_path = tailwindcss_install_dir
exe_file = File.expand_path(File.join(tailwindcss_install_dir, "tailwindcss"))
else
raise DirectoryNotFoundException, <<~MESSAGE
TAILWINDCSS_INSTALL_DIR is set to #{tailwindcss_install_dir}, but that directory does not exist.
MESSAGE
end
else
if Tailwindcss::Upstream::NATIVE_PLATFORMS.keys.none? { |p| Gem::Platform.match(Gem::Platform.new(p)) }
raise UnsupportedPlatformException, <<~MESSAGE
tailwindcss-rails does not support the #{platform} platform
Please install tailwindcss following instructions at https://tailwindcss.com/docs/installation
MESSAGE
end

exe_file = Dir.glob(File.expand_path(File.join(exe_path, "*", "tailwindcss"))).find do |f|
Gem::Platform.match(Gem::Platform.new(File.basename(File.dirname(f))))
end
end

if exe_path.nil?
if exe_file.nil? || !File.exist?(exe_file)
raise ExecutableNotFoundException, <<~MESSAGE
Cannot find the tailwindcss executable for #{platform} in #{exe_path}

@@ -52,7 +69,7 @@ def executable(
MESSAGE
end

exe_path
exe_file
end

def compile_command(debug: false, **kwargs)
78 changes: 73 additions & 5 deletions test/lib/tailwindcss/commands_test.rb
Original file line number Diff line number Diff line change
@@ -2,11 +2,6 @@
require "minitest/mock"

class Tailwindcss::CommandsTest < ActiveSupport::TestCase
test ".platform is a string containing just the cpu and os (not the version)" do
expected = "#{Gem::Platform.local.cpu}-#{Gem::Platform.local.os}"
assert_equal(expected, Tailwindcss::Commands.platform)
end

def mock_exe_directory(platform)
Dir.mktmpdir do |dir|
FileUtils.mkdir(File.join(dir, platform))
@@ -18,6 +13,19 @@ def mock_exe_directory(platform)
end
end

def mock_local_tailwindcss_install
Dir.mktmpdir do |dir|
path = File.join(dir, "tailwindcss")
FileUtils.touch(path)
yield(dir, path)
end
end

test ".platform is a string containing just the cpu and os (not the version)" do
expected = "#{Gem::Platform.local.cpu}-#{Gem::Platform.local.os}"
assert_equal(expected, Tailwindcss::Commands.platform)
end

test ".executable returns the absolute path to the binary" do
mock_exe_directory("sparc-solaris2.8") do |dir, executable|
expected = File.expand_path(File.join(dir, "sparc-solaris2.8", "tailwindcss"))
@@ -42,6 +50,66 @@ def mock_exe_directory(platform)
end
end

test ".executable returns the executable in TAILWINDCSS_INSTALL_DIR when no packaged binary exists" do
mock_local_tailwindcss_install do |local_install_dir, expected|
result = nil
begin
ENV["TAILWINDCSS_INSTALL_DIR"] = local_install_dir
assert_output(nil, /using TAILWINDCSS_INSTALL_DIR/) do
result = Tailwindcss::Commands.executable(exe_path: "/does/not/exist")
end
ensure
ENV["TAILWINDCSS_INSTALL_DIR"] = nil
end
assert_equal(expected, result)
end
end

test ".executable returns the executable in TAILWINDCSS_INSTALL_DIR when we're not on a supported platform" do
Gem::Platform.stub(:match, false) do # nothing is supported
mock_local_tailwindcss_install do |local_install_dir, expected|
result = nil
begin
ENV["TAILWINDCSS_INSTALL_DIR"] = local_install_dir
assert_output(nil, /using TAILWINDCSS_INSTALL_DIR/) do
result = Tailwindcss::Commands.executable
end
ensure
ENV["TAILWINDCSS_INSTALL_DIR"] = nil
end
assert_equal(expected, result)
end
end
end

test ".executable returns the executable in TAILWINDCSS_INSTALL_DIR even when a packaged binary exists" do
mock_exe_directory("sparc-solaris2.8") do |dir, _executable|
mock_local_tailwindcss_install do |local_install_dir, expected|
result = nil
begin
ENV["TAILWINDCSS_INSTALL_DIR"] = local_install_dir
assert_output(nil, /using TAILWINDCSS_INSTALL_DIR/) do
result = Tailwindcss::Commands.executable(exe_path: dir)
end
ensure
ENV["TAILWINDCSS_INSTALL_DIR"] = nil
end
assert_equal(expected, result)
end
end
end

test ".executable raises ExecutableNotFoundException is TAILWINDCSS_INSTALL_DIR is set to a nonexistent dir" do
begin
ENV["TAILWINDCSS_INSTALL_DIR"] = "/does/not/exist"
assert_raises(Tailwindcss::Commands::DirectoryNotFoundException) do
Tailwindcss::Commands.executable
end
ensure
ENV["TAILWINDCSS_INSTALL_DIR"] = nil
end
end

test ".compile_command" do
mock_exe_directory("sparc-solaris2.8") do |dir, executable|
Rails.stub(:root, File) do # Rails.root won't work in this test suite