diff --git a/README.md b/README.md index 8d7e9cef..896bbaa7 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ for changes and roadmap. ### Getting started -Right-click any `.less`, `.scss`, `.styl`, `.jsx`, `.es6` or `.coffee` file in Solution Explorer to +Right-click any `.less`, `.scss`, `.styl`, `.jsx`, `.es6`, `.coffee`, or `.css` file in Solution Explorer to setup compilation. ![Compile file](art/contextmenu-compile.png) @@ -46,7 +46,7 @@ run all the configured compilers. ### Compile on save -Any time a `.less`, `.scss`, `.styl`, `.jsx`, `.es6` or `.coffee` file is modified within +Any time a `.less`, `.scss`, `.styl`, `.jsx`, `.es6`, `.coffee`, or `.css` file is modified within Visual Studio, the compiler runs automatically to produce the compiled output file. The same is true when saving the `compilerconfig.json` file where @@ -133,7 +133,18 @@ Here's an example of what that file looks like: "options":{ "sourceMap": true } + }, + { + "outputFile": "output/tailwind.css", + "inputFile": "input/tailwind.css", + "minify": { + "enabled": true + }, + "includeInProject": true, + "options":{ + "sourceMap": true + } } ] ``` -Default values for `compilerconfig.json` can be found in the `compilerconfig.json.defaults` file in the same location. \ No newline at end of file +Default values for `compilerconfig.json` can be found in the `compilerconfig.json.defaults` file in the same location. diff --git a/src/WebCompiler/Compile/CompilerService.cs b/src/WebCompiler/Compile/CompilerService.cs index 119b7ff0..f16c3303 100644 --- a/src/WebCompiler/Compile/CompilerService.cs +++ b/src/WebCompiler/Compile/CompilerService.cs @@ -15,7 +15,7 @@ public static class CompilerService private static object _syncRoot = new object(); // Used to lock on the initialize step /// A list of allowed file extensions. - public static readonly string[] AllowedExtensions = new[] { ".LESS", ".SCSS", ".SASS", ".STYL", ".COFFEE", ".ICED", ".JS", ".JSX", ".ES6", ".HBS", ".HANDLEBARS" }; + public static readonly string[] AllowedExtensions = new[] { ".LESS", ".SCSS", ".SASS", ".STYL", ".COFFEE", ".ICED", ".JS", ".JSX", ".ES6", ".HBS", ".HANDLEBARS", ".CSS" }; /// /// Test if a file type is supported by the compilers. @@ -67,6 +67,10 @@ internal static ICompiler GetCompiler(Config config) case ".ES6": compiler = new BabelCompiler(_path); break; + + case ".CSS": + compiler = new TailwindCompiler(_path); + break; } return compiler; diff --git a/src/WebCompiler/Compile/TailwindCompiler.cs b/src/WebCompiler/Compile/TailwindCompiler.cs new file mode 100644 index 00000000..aa383077 --- /dev/null +++ b/src/WebCompiler/Compile/TailwindCompiler.cs @@ -0,0 +1,121 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace WebCompiler +{ + class TailwindCompiler : ICompiler + { + private static Regex _errorRx = new Regex(@".+\.css:\s(?.+)\((?[0-9]+):(?[0-9]+)\)", RegexOptions.Compiled); + private string _path; + private string _output = string.Empty; + private string _error = string.Empty; + + public TailwindCompiler(string path) + { + _path = path; + } + + public CompilerResult Compile(Config config) + { + string baseFolder = Path.GetDirectoryName(config.FileName); + string inputFile = Path.Combine(baseFolder, config.InputFile); + + FileInfo info = new FileInfo(inputFile); + string content = File.ReadAllText(info.FullName); + + CompilerResult result = new CompilerResult + { + FileName = info.FullName, + OriginalContent = content, + }; + + try + { + RunCompilerProcess(config, info); + + result.CompiledContent = _output; + + if (_error.Length > 0) + { + CompilerError ce = new CompilerError + { + FileName = info.FullName, + Message = _error.Replace(baseFolder, string.Empty), + IsWarning = !string.IsNullOrEmpty(_output) + }; + + var match = _errorRx.Match(_error); + + if (match.Success) + { + ce.Message = match.Groups["message"].Value.Replace(baseFolder, string.Empty); + ce.LineNumber = int.Parse(match.Groups["line"].Value); + ce.ColumnNumber = int.Parse(match.Groups["column"].Value); + } + + result.Errors.Add(ce); + } + } + catch (Exception ex) + { + CompilerError error = new CompilerError + { + FileName = info.FullName, + Message = string.IsNullOrEmpty(_error) ? ex.Message : _error, + LineNumber = 0, + ColumnNumber = 0, + }; + + result.Errors.Add(error); + } + + return result; + } + + private void RunCompilerProcess(Config config, FileInfo info) + { + string arguments = ConstructArguments(config); + + ProcessStartInfo start = new ProcessStartInfo + { + WorkingDirectory = info.Directory.FullName, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + FileName = "cmd.exe", + Arguments = $"/c \"\"{Path.Combine(_path, "node_modules\\.bin\\tailwind.cmd")}\" {arguments} \"{info.FullName}\"\"", + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + + start.EnvironmentVariables["PATH"] = _path + ";" + start.EnvironmentVariables["PATH"]; + + using (Process p = Process.Start(start)) + { + var stdout = p.StandardOutput.ReadToEndAsync(); + var stderr = p.StandardError.ReadToEndAsync(); + p.WaitForExit(); + + _output = stdout.Result; + _error = stderr.Result; + } + } + + private static string ConstructArguments(Config config) + { + string arguments = "build"; + + var options = TailwindOptions.FromConfig(config); + + if (options.SourceMap || config.SourceMap) + arguments += " --source-maps"; + + return arguments; + } + } +} diff --git a/src/WebCompiler/Config/ConfigHandler.cs b/src/WebCompiler/Config/ConfigHandler.cs index 5b827bde..955bf2c7 100644 --- a/src/WebCompiler/Config/ConfigHandler.cs +++ b/src/WebCompiler/Config/ConfigHandler.cs @@ -68,6 +68,7 @@ public void CreateDefaultsFile(string fileName) babel = new BabelOptions(), coffeescript = new IcedCoffeeScriptOptions(), handlebars = new HandlebarsOptions(), + tailwindcss = new TailwindOptions(), }, minifiers = new {