-
Notifications
You must be signed in to change notification settings - Fork 3
/
lib.rs
227 lines (206 loc) · 7.73 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! This is a simple plugin for Perseus that runs the Tailwind CLI at build time.
//! It will automatically download the newest version of the CLI initialize the project to look for
//! class names in Rust files in `src` and HTML files in `static`.
//! Further configuration can be done as usual in `tailwind.config.js`.
//!
//! # Usage
//!
//! Add the plugin to you Perseus App in your Perseus main function.
//!
//! ```
//! # use perseus::PerseusApp;
//! # use perseus::plugins::Plugins;
//! PerseusApp::new()
//! .plugins(Plugins::new().plugin(
//! perseus_tailwind::get_tailwind_plugin,
//! // Don't put this in /static, it will trigger build loops.
//! // Put this in /dist or a custom folder and use a static alias instead.
//! perseus_tailwind::TailwindOptions::new("dist/tailwind.css")
//! ))
//! .static_alias("/tailwind.css", "dist/tailwind.css")
//! # ;
//! ```
//!
//! If you're already using plugins just add the plugin to your `Plugins` as usual.
//!
//! # Using a custom binary
//!
//! If you for some reason want to use a specific version of the CLI or some other CLI with the same
//! command line interface entirely, just place the binary with its default system-specific name
//! (i.e. `tailwindcss-linux-arm64`) in the project directory.
//!
//! # Stability
//!
//! The plugin is fairly simple and shouldn't break anything since it just executes the Tailwind CLI.
//! The download and installation should work on Linux, MacOS and Windows on all architectures that
//! are supported by Tailwind, but is currently only tested on Windows x64.
#[cfg(engine)]
use perseus::plugins::PluginAction;
use perseus::plugins::{empty_control_actions_registrar, Plugin, PluginEnv};
#[cfg(engine)]
use std::{fs::File, io::Write, path::PathBuf, process::Command};
static PLUGIN_NAME: &str = "tailwind-plugin";
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
static BINARY_NAME: &str = "tailwindcss-linux-arm64";
#[cfg(all(target_os = "linux", target_arch = "arm"))]
static BINARY_NAME: &str = "tailwindcss-linux-armv7";
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
static BINARY_NAME: &str = "tailwindcss-linux-x64";
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
static BINARY_NAME: &str = "tailwindcss-macos-arm64";
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
static BINARY_NAME: &str = "tailwindcss-macos-x64";
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
static BINARY_NAME: &str = "tailwindcss-windows-x64.exe";
/// Options for the Tailwind CLI
#[derive(Debug, Default)]
pub struct TailwindOptions {
/// The path to the input CSS file
pub in_file: Option<String>,
/// The path to the CSS file output by the CLI.\
/// **DO NOT PUT THIS IN `/static` UNLESS YOU LIKE BUILD LOOPS!**\
/// Always put it somewhere in `/dist` use static aliases instead.\
pub out_file: String,
/// A custom config path for the CLI
pub config_path: Option<String>,
}
impl TailwindOptions {
/// Create a new options struct with only the out path set
///
/// # Out Path
/// The path to the CSS file output by the CLI.\
/// **DO NOT PUT THIS IN `/static` UNLESS YOU LIKE BUILD LOOPS!**\
/// Always put it somewhere in `/dist` use static aliases instead.\
pub fn new(out_file: impl Into<String>) -> Self {
TailwindOptions {
out_file: out_file.into(),
..Default::default()
}
}
/// Set the input file for the tailwind CLI
pub fn in_file(mut self, in_file: impl Into<String>) -> Self {
self.in_file = Some(in_file.into());
self
}
pub fn config(mut self, config_path: impl Into<String>) -> Self {
self.config_path = Some(config_path.into());
self
}
}
/// The plugin constructor
pub fn get_tailwind_plugin() -> Plugin<TailwindOptions> {
#[allow(unused_mut)]
Plugin::new(
PLUGIN_NAME,
|mut actions| {
#[cfg(engine)]
{
actions
.build_actions
.before_build
.register_plugin(PLUGIN_NAME, |_, data| {
let options = data.downcast_ref::<TailwindOptions>().unwrap();
try_run_tailwind(options)?;
Ok(())
});
actions
.export_actions
.before_export
.register_plugin(PLUGIN_NAME, |_, data| {
let options = data.downcast_ref::<TailwindOptions>().unwrap();
try_run_tailwind(options)?;
Ok(())
});
}
actions
},
empty_control_actions_registrar,
PluginEnv::Server,
)
}
#[cfg(engine)]
fn try_run_tailwind(options: &TailwindOptions) -> Result<(), String> {
let cli = PathBuf::from(BINARY_NAME);
if !cli.exists() {
install_tailwind_cli()?;
}
let config_path = options
.config_path
.as_ref()
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("tailwind.config.js"));
if !config_path.exists() {
init_tailwind()?;
}
let mut args = vec!["-o", &options.out_file];
if cfg!(not(debug_assertions)) {
args.push("--minify");
}
if let Some(in_file) = &options.in_file {
args.extend(["-i", in_file]);
}
if let Some(config) = &options.config_path {
args.extend(["-c", config]);
}
let child = Command::new(format!("./{BINARY_NAME}"))
.args(args)
.spawn()
.map_err(|_| "Failed to run Tailwind CLI")?;
let output = child
.wait_with_output()
.map_err(|_| "Failed to wait on Tailwind CLI")?;
let output = String::from_utf8_lossy(&output.stdout);
// Try to figure out if there was an error
if output.contains('{') {
return Err(output.to_string());
}
Ok(())
}
#[cfg(engine)]
fn install_tailwind_cli() -> Result<(), String> {
log::info!("Tailwind CLI not found, installing...");
log::info!("Downloading binary for this platform...");
let url = format!(
"https://github.com/tailwindlabs/tailwindcss/releases/latest/download/{BINARY_NAME}"
);
let binary = tokio::task::block_in_place(move || {
reqwest::blocking::get(url)
.map_err(|_| {
"Failed to download binary. Check it's still available on the tailwind GitHub."
})?
.bytes()
.map_err(|_| "Failed to read binary content of the tailwind binary download")
})?;
log::info!("Writing to disk as {BINARY_NAME}...");
let mut file = File::create(BINARY_NAME).map_err(|_| "Failed to create binary file")?;
file.write_all(&binary)
.map_err(|_| "Failed to write binary to disk")?;
#[cfg(target_family = "unix")]
{
println!("Making the binary executable...");
use std::os::unix::fs::PermissionsExt;
let mut perms = file
.metadata()
.map_err(|_| "Failed to get metadata for binary to set executable permission")?
.permissions();
let mode = perms.mode() | 0o555;
perms.set_mode(mode);
file.set_permissions(perms)
.map_err(|e| format!("Failed to set permissions: {e}"))?;
}
println!("Done installing Tailwind CLI.");
Ok(())
}
#[cfg(engine)]
fn init_tailwind() -> Result<(), String> {
log::info!(
"Initializing Tailwind to search all Rust files in 'src' and all HTML files in 'static'."
);
let default_config = include_bytes!("default-config.js");
let mut config =
File::create("tailwind.config.js").map_err(|_| "Failed to create config file")?;
config
.write_all(default_config)
.map_err(|_| "Failed to write default config")?;
Ok(())
}