Skip to content

Commit

Permalink
Compilers: Extract LESS & SASS SourceMap code
Browse files Browse the repository at this point in the history
Includes ugly hack for sass/libsass#242
  • Loading branch information
SLaks committed Jan 14, 2014
1 parent 2cc8345 commit d6b8dd8
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 156 deletions.
100 changes: 100 additions & 0 deletions EditorExtensions/Compilers/CssCompilerBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Helpers;
using MadsKristensen.EditorExtensions.Helpers;
using Microsoft.CSS.Core;

namespace MadsKristensen.EditorExtensions.Compilers
{
///<summary>A base class for a compiler that rewrites CSS source maps.</summary>
public abstract class CssCompilerBase : NodeExecutorBase
{
private static readonly Regex _sourceMapInCss = new Regex(@"\/\*#([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/", RegexOptions.Multiline);

protected abstract bool GenerateSourceMap { get; }


protected override string PostProcessResult(string resultSource, string sourceFileName, string targetFileName)
{
// Inserts an empty row between each rule and replace two space indentation with 4 space indentation
resultSource = UpdateSourceMapUrls(resultSource, targetFileName);

var message = ServiceName + ": " + Path.GetFileName(sourceFileName) + " compiled.";

// If the caller wants us to renormalize URLs to a different filename, do so.
if (targetFileName != null && Path.GetDirectoryName(targetFileName) != Path.GetDirectoryName(sourceFileName)
&& resultSource.IndexOf("url(", StringComparison.OrdinalIgnoreCase) > 0)
{
try
{
resultSource = CssUrlNormalizer.NormalizeUrls(
tree: new CssParser().Parse(resultSource, true),
targetFile: targetFileName,
oldBasePath: sourceFileName
);
}
catch (Exception ex)
{
message = ServiceName + ": An error occurred while normalizing generated paths in " + sourceFileName + "\r\n" + ex;
}
}

Logger.Log(message);

return resultSource;
}


private string UpdateSourceMapUrls(string content, string compiledFileName)
{
if (!GenerateSourceMap || !File.Exists(compiledFileName))
return content;

string sourceMapFilename = compiledFileName + ".map";

if (!File.Exists(sourceMapFilename))
return content;

var updatedFileContent = GetUpdatedSourceMapFileContent(compiledFileName, sourceMapFilename);

if (updatedFileContent == null)
return content;

FileHelpers.WriteFile(updatedFileContent, sourceMapFilename);
ProjectHelpers.AddFileToProject(compiledFileName, sourceMapFilename);

return UpdateSourceLinkInCssComment(content, FileHelpers.RelativePath(compiledFileName, sourceMapFilename));
}

// Overridden to work around SASS bug
// TODO: Remove when https://github.com/hcatlin/libsass/issues/242 is fixed
protected virtual string ReadMapFile(string sourceMapFilename) { return File.ReadAllText(sourceMapFilename); }

private string GetUpdatedSourceMapFileContent(string cssFileName, string sourceMapFilename)
{
// Read JSON map file and deserialize.
dynamic jsonSourceMap = Json.Decode(ReadMapFile(sourceMapFilename));

if (jsonSourceMap == null)
return null;

jsonSourceMap.sources = ((IEnumerable<dynamic>)jsonSourceMap.sources).Select(s => FileHelpers.RelativePath(cssFileName, s));
jsonSourceMap.names = new List<dynamic>(jsonSourceMap.names);
jsonSourceMap.file = Path.GetFileName(cssFileName);

return Json.Encode(jsonSourceMap);
}

private static string UpdateSourceLinkInCssComment(string content, string sourceMapRelativePath)
{ // Fix sourceMappingURL comment in CSS file with network accessible path.
return _sourceMapInCss.Replace(content,
string.Format(CultureInfo.InvariantCulture, "/*# sourceMappingURL={0} */", sourceMapRelativePath));
}
}
}
87 changes: 6 additions & 81 deletions EditorExtensions/Compilers/LESS/LessCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,109 +10,34 @@
using Microsoft.CSS.Core;
using Microsoft.VisualStudio.Utilities;

namespace MadsKristensen.EditorExtensions
namespace MadsKristensen.EditorExtensions.Compilers
{
[ContentType("LESS")]
public class LessCompiler : NodeExecutorBase
public class LessCompiler : CssCompilerBase
{
private static readonly string _compilerPath = Path.Combine(WebEssentialsResourceDirectory, @"nodejs\tools\node_modules\less\bin\lessc");
private static readonly Regex _errorParsingPattern = new Regex(@"^(?<message>.+) in (?<fileName>.+) on line (?<line>\d+), column (?<column>\d+):$", RegexOptions.Multiline);
private static readonly Regex _sourceMapInCss = new Regex(@"\/\*#([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/", RegexOptions.Multiline);

public override string TargetExtension { get { return ".css"; } }
public override string ServiceName { get { return "LESS"; } }

protected override string CompilerPath { get { return _compilerPath; } }
protected override Regex ErrorParsingPattern { get { return _errorParsingPattern; } }

protected override bool GenerateSourceMap { get { return WESettings.Instance.Less.GenerateSourceMaps; } }
protected override string GetArguments(string sourceFileName, string targetFileName)
{
var args = new StringBuilder("--no-color --relative-urls ");

if (WESettings.Instance.Less.GenerateSourceMaps)
{
string baseFolder = null;
if (!InUnitTests)
baseFolder = ProjectHelpers.GetProjectFolder(targetFileName);
baseFolder = baseFolder ?? Path.GetDirectoryName(targetFileName);

args.AppendFormat(CultureInfo.CurrentCulture, "--source-map-basepath=\"{0}\" --source-map=\"{1}.map\" ",
baseFolder.Replace("\\", "/"), targetFileName);
Path.GetDirectoryName(targetFileName), targetFileName);
}

args.AppendFormat(CultureInfo.CurrentCulture, "\"{0}\" \"{1}\"", sourceFileName, targetFileName);

return args.ToString();
}

protected override string PostProcessResult(string resultSource, string sourceFileName, string targetFileName)
{
// Inserts an empty row between each rule and replace two space indentation with 4 space indentation
resultSource = UpdateSourceMapUrls(resultSource, targetFileName);

var message = "LESS: " + Path.GetFileName(sourceFileName) + " compiled.";

// If the caller wants us to renormalize URLs to a different filename, do so.
if (targetFileName != null && Path.GetDirectoryName(targetFileName) != Path.GetDirectoryName(sourceFileName)
&& resultSource.IndexOf("url(", StringComparison.OrdinalIgnoreCase) > 0)
{
try
{
resultSource = CssUrlNormalizer.NormalizeUrls(
tree: new CssParser().Parse(resultSource, true),
targetFile: targetFileName,
oldBasePath: sourceFileName
);
}
catch (Exception ex)
{
message = "LESS: An error occurred while normalizing generated paths in " + sourceFileName + "\r\n" + ex;
}
}

Logger.Log(message);

return resultSource;
}

private static string UpdateSourceMapUrls(string content, string compiledFileName)
{
if (!WESettings.Instance.Less.GenerateSourceMaps || !File.Exists(compiledFileName))
return content;

string sourceMapFilename = compiledFileName + ".map";

if (!File.Exists(sourceMapFilename))
return content;

var updatedFileContent = GetUpdatedSourceMapFileContent(compiledFileName, sourceMapFilename);

if (updatedFileContent == null)
return content;

FileHelpers.WriteFile(updatedFileContent, sourceMapFilename);
ProjectHelpers.AddFileToProject(compiledFileName, sourceMapFilename);

return UpdateSourceLinkInCssComment(content, FileHelpers.RelativePath(compiledFileName, sourceMapFilename));
}

private static string GetUpdatedSourceMapFileContent(string cssFileName, string sourceMapFilename)
{
// Read JSON map file and deserialize.
dynamic jsonSourceMap = Json.Decode(File.ReadAllText(sourceMapFilename));

if (jsonSourceMap == null)
return null;

jsonSourceMap.sources = ((IEnumerable<dynamic>)jsonSourceMap.sources).Select(s => FileHelpers.RelativePath(cssFileName, s));
jsonSourceMap.names = new List<dynamic>(jsonSourceMap.names);
jsonSourceMap.file = Path.GetFileName(cssFileName);

return Json.Encode(jsonSourceMap);
}

private static string UpdateSourceLinkInCssComment(string content, string sourceMapRelativePath)
{ // Fix sourceMappingURL comment in CSS file with network accessible path.
return _sourceMapInCss.Replace(content,
string.Format(CultureInfo.CurrentCulture, "/*# sourceMappingURL={0} */", sourceMapRelativePath));
}
}
}
84 changes: 9 additions & 75 deletions EditorExtensions/Compilers/SASS/SassCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,26 @@
using Microsoft.CSS.Core;
using Microsoft.VisualStudio.Utilities;

namespace MadsKristensen.EditorExtensions
namespace MadsKristensen.EditorExtensions.Compilers
{
[ContentType("SASS")]
public class SassCompiler : NodeExecutorBase
public class SassCompiler : CssCompilerBase
{
private static readonly string _compilerPath = Path.Combine(WebEssentialsResourceDirectory, @"nodejs\tools\node_modules\node-sass\bin\node-sass");
private static readonly Regex _errorParsingPattern = new Regex(@"(?<fileName>.*):(?<line>.\d*): error: (?<message>.*\n.*)", RegexOptions.Multiline);
private static readonly Regex _sourceMapInCss = new Regex(@"\/\*#([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/", RegexOptions.Multiline);

public override string ServiceName { get { return "SASS"; } }
public override string TargetExtension { get { return ".css"; } }

protected override string CompilerPath { get { return _compilerPath; } }
protected override Regex ErrorParsingPattern { get { return _errorParsingPattern; } }

protected override bool GenerateSourceMap { get { return WESettings.Instance.Sass.GenerateSourceMaps; } }
protected override string GetArguments(string sourceFileName, string targetFileName)
{
var args = new StringBuilder();

if (WESettings.Instance.Sass.GenerateSourceMaps)
if (GenerateSourceMap)
{
args.Append("--source-map ");
}
Expand All @@ -36,78 +38,10 @@ protected override string GetArguments(string sourceFileName, string targetFileN

return args.ToString();
}

protected override string PostProcessResult(string resultSource, string sourceFileName, string targetFileName)
{
resultSource = UpdateSourceMapUrls(resultSource, targetFileName);

var message = "SASS: " + Path.GetFileName(sourceFileName) + " compiled.";

// If the caller wants us to renormalize URLs to a different filename, do so.
if (targetFileName != null && resultSource.IndexOf("url(", StringComparison.OrdinalIgnoreCase) > 0)
{
try
{
resultSource = CssUrlNormalizer.NormalizeUrls(
tree: new CssParser().Parse(resultSource, true),
targetFile: targetFileName,
oldBasePath: sourceFileName
);
}
catch (Exception ex)
{
message = "SASS: An error occurred while normalizing generated paths in " + sourceFileName + "\r\n" + ex;
}
}

Logger.Log(message);

return resultSource;
}

private static string UpdateSourceMapUrls(string content, string compiledFileName)
//https://github.com/hcatlin/libsass/issues/242
protected override string ReadMapFile(string sourceMapFilename)
{
if (WESettings.Instance.Sass.GenerateSourceMaps || !File.Exists(compiledFileName))
return content;

string sourceMapFilename = compiledFileName + ".map";

if (!File.Exists(sourceMapFilename))
return content;

var updatedFileContent = GetUpdatedSourceMapFileContent(compiledFileName, sourceMapFilename);

if (updatedFileContent == null)
return content;

FileHelpers.WriteFile(updatedFileContent, sourceMapFilename);
ProjectHelpers.AddFileToProject(compiledFileName, sourceMapFilename);

return UpdateSourceLinkInCssComment(content, FileHelpers.RelativePath(compiledFileName, sourceMapFilename));
}

private static string GetUpdatedSourceMapFileContent(string cssFileName, string sourceMapFilename)
{
// Should be removed when this is fixed: https://github.com/hcatlin/libsass/issues/242
var sourceMapFileContent = File.ReadAllText(sourceMapFilename).Replace("\\", "\\\\");

// Read JSON map file and deserialize.
dynamic jsonSourceMap = Json.Decode(sourceMapFileContent);

if (jsonSourceMap == null)
return null;

jsonSourceMap.sources = ((IEnumerable<dynamic>)jsonSourceMap.sources).Select(s => FileHelpers.RelativePath(cssFileName, s));
jsonSourceMap.names = new List<dynamic>(jsonSourceMap.names);
jsonSourceMap.file = Path.GetFileName(cssFileName);

return Json.Encode(jsonSourceMap);
}

private static string UpdateSourceLinkInCssComment(string content, string sourceMapRelativePath)
{ // Fix sourceMappingURL comment in CSS file with network accessible path.
return _sourceMapInCss.Replace(content,
string.Format(CultureInfo.CurrentCulture, "/*# sourceMappingURL={0} */", sourceMapRelativePath));
return File.ReadAllText(sourceMapFilename).Replace("\\", "\\\\");
}
}
}
1 change: 1 addition & 0 deletions EditorExtensions/WebEssentials2013.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
<Compile Include="Commands\JavaScript\CommentCompletionCommandTarget.cs" />
<Compile Include="Commands\TypeScript\TypeScriptFindReferencesCommandTarget.cs" />
<Compile Include="Commands\TypeScript\TypeScriptCreationListener.cs" />
<Compile Include="Compilers\CssCompilerBase.cs" />
<Compile Include="Compilers\Lint\LintConfigEncodingFix.cs" />
<Compile Include="Commands\LintFileInvoker.cs" />
<Compile Include="Commands\LintReporter.cs" />
Expand Down

0 comments on commit d6b8dd8

Please sign in to comment.