using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DeployTools { using System.Diagnostics; using System.IO; using System.Reflection; using System.Security.Cryptography; using System.Text.RegularExpressions; using Newtonsoft.Json; class Program { static string _projectRoot; static string _sourceDirectory; static string _destinationDirectory; static string _ajaxminiTool; static Dictionary _sourceFiles; static Dictionary _moduleMap; static void Main(string[] args) { _projectRoot = Path.GetFullPath(Path.Combine(Assembly.GetExecutingAssembly().Location, @"..\..\")); _sourceDirectory = Path.Combine(_projectRoot, "Web12306"); _destinationDirectory = Path.Combine(_projectRoot, "wwwroot"); _ajaxminiTool = Path.Combine(_projectRoot, "BuildTools\\AjaxMinifier.exe"); _moduleMap = new Dictionary(StringComparer.OrdinalIgnoreCase); Console.WriteLine("使用压缩工具 {0}", _ajaxminiTool); Console.WriteLine(); //1.创建目标目录 Console.WriteLine("正在准备目标目录...."); try { if (Directory.Exists(_destinationDirectory)) Directory.Delete(_destinationDirectory, true); } catch (Exception ex) { } Directory.CreateDirectory(_destinationDirectory); Console.WriteLine(); //2.创建文件列表 Console.WriteLine("正在扫描文件列表..."); CreateFileList(); Console.WriteLine("文件总数: {0}", _sourceFiles.Count); Console.WriteLine(); //3.复制文件 Console.WriteLine("正在将文件复制到目标目录..."); CopySourceFiles(); //4.合并处理css文件 Console.WriteLine("正在合并样式表文件..."); ProcessCss("css\\index.css"); Console.WriteLine(); //5.处理js文件 ProcessScriptFiles(); //6.合并js文件 CombineScript(); //7.压缩html ProcessHtml(); //8.写入记录 WriteModuleMap(); //9.清理目录 CleanupDirectory(); Console.WriteLine(_projectRoot); Console.ReadKey(); } static void CleanupDirectory() { Console.WriteLine("正在清理目标目录..."); foreach (var directory in Directory.GetDirectories(_destinationDirectory)) { CleanupDirectory(directory); } Console.WriteLine("清理目标目录完成"); Console.WriteLine(); } static void CleanupDirectory(string path) { var directorys = Directory.GetDirectories(path); foreach (var directory in directorys) { CleanupDirectory(directory); } if (Directory.GetDirectories(path).Length > 0 || Directory.GetFiles(path).Length > 0) return; Directory.Delete(path); Console.WriteLine("\t已删除空目录 {0}", path); } static void WriteModuleMap() { Console.WriteLine("正在写入模块列表记录..."); var file = "modules.json"; var fullFile = Path.Combine(_projectRoot, file); File.WriteAllText(fullFile, JsonConvert.SerializeObject(_moduleMap.Select(s => new { module = s.Key, path = s.Value }).ToArray())); Console.WriteLine("模块列表记录完成"); Console.WriteLine(); } static void ProcessHtml() { Console.WriteLine("正在处理HTML页面文件..."); var htmlFiles = new[] { "index.html" }; foreach (var htmlFile in htmlFiles) { Console.Write("正在处理文件 {0} ...", htmlFile); ProcessHtml(htmlFile); Console.WriteLine("完成"); } Console.WriteLine("HTML页面文件处理完成"); Console.WriteLine(); } static void ProcessHtml(string file) { var fileFull = Path.GetFullPath(Path.Combine(_destinationDirectory, file)); var content = File.ReadAllText(fileFull); //处理模板 content = Regex.Replace(content, @"]*?type=['""]?text/x-dot-template['""]?[^>]*?>[\w\W]+?", _ => { var tpl = _.Value; return Regex.Replace(tpl, @"[\t\s\r\n]{2,}", " "); }, RegexOptions.IgnoreCase); //release tag content = content.Replace("$BUILD_DATE$", DateTime.Now.ToString("yyyyMMddHHmmss")); File.WriteAllText(fileFull, content); //使用uglify 压缩 var psi = new ProcessStartInfo(System.Configuration.ConfigurationManager.AppSettings["minifyCmdPath"], "\"" + fileFull + "\"") { WindowStyle = ProcessWindowStyle.Hidden, RedirectStandardOutput = true, UseShellExecute = false, StandardOutputEncoding = Encoding.UTF8 }; var p = Process.Start(psi); content = p.StandardOutput.ReadToEnd(); File.WriteAllText(fileFull, content); } static void CombineScript() { Console.WriteLine("正在合并脚本文件..."); var targetHtml = "index.html"; var targetFullHtml = Path.Combine(_destinationDirectory, targetHtml); var targetContent = File.ReadAllText(targetFullHtml); var scripts = _sourceFiles.Keys.ToArray().Where(s => s.EndsWith(".js", StringComparison.OrdinalIgnoreCase)).ToList(); //处理script标签 var allScriptTags = new List(); targetContent = Regex.Replace(targetContent, @"]+?)['""\s>].*?", _ => { allScriptTags.Add(Path.GetFullPath(Path.Combine(_destinationDirectory, _.Groups[1].Value))); return string.Empty; }); //插入新的脚本 targetContent = targetContent.Replace("", ""); File.WriteAllText(targetFullHtml, targetContent); //合并脚本库 var extLibs = allScriptTags.Take(allScriptTags.Count - 1).ToArray(); var extLibContent = string.Join(";", extLibs.Select(s => { var path = Path.Combine(_destinationDirectory, s); var content = File.ReadAllText(path); File.Delete(path); return content; })); //页面脚本 var scriptsHash = new HashSet(allScriptTags, StringComparer.OrdinalIgnoreCase); var pageLibContent = string.Join(";", scripts.Select(s => { var path = Path.GetFullPath(Path.Combine(_destinationDirectory, s)); if (scriptsHash.Contains(path)) return string.Empty; var content = File.ReadAllText(path); File.Delete(path); return content; }).Where(s => !string.IsNullOrEmpty(s))); //引导脚本 var bootScriptFile = Path.GetFullPath(Path.Combine(_destinationDirectory, allScriptTags.Last())); var bootScript = File.ReadAllText(bootScriptFile); File.Delete(bootScriptFile); var extLibFile = "js/lib.js"; var extLibFullFile = Path.Combine(_destinationDirectory, extLibFile); File.WriteAllText(extLibFullFile, string.Join(";", new[] { extLibContent, pageLibContent, bootScript })); Console.WriteLine("脚本文件合并完成"); Console.WriteLine(); } static void ProcessScriptFiles() { Console.WriteLine("正在处理脚本文件..."); foreach (var file in _sourceFiles.Where(s => s.Key.EndsWith(".js", StringComparison.OrdinalIgnoreCase))) { RewriteModuleId(file.Key); //压缩 CompressScript(file.Key); } Console.WriteLine("脚本文件处理完成"); Console.WriteLine(); } static void CompressScript(string filepath) { Console.Write("正在压缩 " + filepath + "..."); filepath = Path.GetFullPath(Path.Combine(_destinationDirectory, filepath)); Process.Start(new ProcessStartInfo(_ajaxminiTool, "-comments:none -esc:true \"" + filepath + "\" -out \"" + filepath + "\"") { WindowStyle = ProcessWindowStyle.Hidden }).WaitForExit(); Console.WriteLine("压缩完成"); } static void RewriteModuleId(string filepath) { Console.Write("正在处理文件 {0}", filepath); filepath = Path.GetFullPath(Path.Combine(_destinationDirectory, filepath)); var folder = Path.GetDirectoryName(filepath); var content = File.ReadAllText(filepath); //是否是模块? var depList = new List(); if (Regex.IsMatch(content, @"^[\r\n\s]*(define\()\s*(function\s*\()", RegexOptions.IgnoreCase)) { var hash = GetModuleId(filepath); Console.WriteLine("正在处理..."); Console.WriteLine("\t模块ID:{0}", hash); //替换内容,并获得所有的模块ID content = Regex.Replace(content, @"([\r\n=\s]require\s*\(\s*['""])([^'""]+)(['""]\))", _ => { var path = Path.GetFullPath(Path.Combine(folder, _.Groups[2].Value)); var id = GetModuleId(path); depList.Add(id); Console.WriteLine("\t依赖模块ID:{0}", id); return _.Groups[1].Value + id + _.Groups[3].Value; }, RegexOptions.IgnoreCase); content = Regex.Replace(content, @"^[\r\n\s]*(define\()\s*(function\s*\()", _ => _.Groups[1].Value + "\"" + hash + "\",[" + string.Join(",", depList.Select(s => "\"" + s + "\"")) + "]," + _.Groups[2].Value, RegexOptions.IgnoreCase); } else { Console.WriteLine("非模块,已跳过"); } content = Regex.Replace(content, @"(seajs\.use\s*\(\s*['""])([^'""]+)(['""]\))", _ => { var path = Path.GetFullPath(Path.Combine(folder, _.Groups[2].Value)); var id = GetModuleId(path); depList.Add(id); Console.WriteLine("\t依赖模块ID:{0}", id); return _.Groups[1].Value + id + _.Groups[3].Value; }, RegexOptions.IgnoreCase); File.WriteAllText(filepath, content); Console.WriteLine("已处理完成"); } static string GetModuleId(string path) { if (!path.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) path += ".js"; if (_moduleMap.ContainsKey(path)) return _moduleMap[path]; var hash = string.Join("", MD5CryptoServiceProvider.Create().ComputeHash(Encoding.UTF8.GetBytes(path.ToLower())).Select(s => s.ToString("X2")).ToArray()); _moduleMap.Add(path, hash); return hash; } static void ProcessCss(string cssfile) { Console.Write("正在处理 " + cssfile + "..."); var content = ImportCssFile(cssfile); var fullPath = Path.Combine(_destinationDirectory, cssfile); //corrent reference content = Regex.Replace(content, @"url\(['""]?(?!data:)([^?'""]+?)(\?[^'""\)]*?)?['""]?\)", _ => { var extag = _.Groups[2].Success ? _.Groups[2].Value : ""; return "url(\"" + GetRelativePath(Path.GetDirectoryName(cssfile), GetTargetSubPath(_.Groups[1].Value)).Replace('\\', '/').ToLower() + extag + "\")"; }, RegexOptions.IgnoreCase); File.WriteAllText(Path.Combine(_destinationDirectory, cssfile), content); Console.WriteLine("合并完成"); Console.Write("正在压缩 " + cssfile + "..."); Process.Start(new ProcessStartInfo(_ajaxminiTool, "-comments:none \"" + fullPath + "\" -out \"" + fullPath + "\"") { WindowStyle = ProcessWindowStyle.Hidden }).WaitForExit(); Console.WriteLine("压缩完成"); } static string GetRelativePath(string basePath, string secondPath) { if (string.IsNullOrEmpty(secondPath) || string.IsNullOrEmpty(basePath)) return secondPath; var ps1 = basePath.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); var ps2 = secondPath.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); var upperBound = Math.Min(ps1.Length, ps2.Length); var startIndex = Enumerable.Range(0, upperBound).FirstOrDefault(s => string.Compare(ps1[s], ps2[s], true) != 0); if (startIndex == 0) return "/" + secondPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); if (ps1.Length == startIndex && ps2.Length <= startIndex) return "./"; return string.Join(Path.AltDirectorySeparatorChar.ToString(), Enumerable.Repeat("..", ps1.Length - startIndex).Concat(ps2.Skip(startIndex)).ToArray()); } static string GetProjectSubPath(string path) { var removeLength = _sourceDirectory.EndsWith("\\") ? _sourceDirectory.Length : _sourceDirectory.Length + 1; return path.Remove(0, removeLength); } static string GetTargetSubPath(string path) { var removeLength = _destinationDirectory.EndsWith("\\") ? _destinationDirectory.Length : _destinationDirectory.Length + 1; return path.Remove(0, removeLength); } /// /// 修正CSS文件引用路径 /// /// 要修正的css文件路径 /// static string ImportCssFile(string filePath) { var fullPath = Path.Combine(_destinationDirectory, filePath); var content = File.ReadAllText(fullPath); var directory = Path.GetDirectoryName(fullPath); File.Delete(fullPath); //import content = Regex.Replace(content, @"@import\s+url\(['""]?(?!data:)([^?'""]+?)(\?[^'""\)]*?)?['""]?\);?", _ => ImportCssFile(Path.GetFullPath(Path.Combine(directory, _.Groups[1].Value.Replace('/', '\\')))), RegexOptions.IgnoreCase); //reference content = Regex.Replace(content, @"url\(['""]?(?!data:)([^?'""]+?)(\?[^'""\)]*?)?['""]?\)", _ => { var extag = _.Groups[2].Success ? _.Groups[2].Value : ""; var path = _.Groups[1].Value.Replace('/', '\\'); if (path[0] == '\\') path = Path.Combine(_destinationDirectory, path.Remove(0, 1)); else path = Path.Combine(directory, path); return string.Format("url(\"{0}{1}\")", Path.GetFullPath(path), extag); }, RegexOptions.IgnoreCase); return content; } static void CreateFileList() { var include = new[] { @".*\.*?\.(jpg|gif|png|html|js|svg|eot|ttf|woff|otf|css|ico)$" }; var fileter = new[] { @"^(scripts|bin|app_data)\.*" }; var allfiles = Directory.GetFiles(_sourceDirectory, "*.*", SearchOption.AllDirectories).ToDictionary(GetProjectSubPath, StringComparer.OrdinalIgnoreCase); //过滤 _sourceFiles = allfiles.Where(s => include.Any(y => Regex.IsMatch(s.Key, y, RegexOptions.IgnoreCase)) && !fileter.Any(y => Regex.IsMatch(s.Key, y, RegexOptions.IgnoreCase))) .ToDictionary(s => s.Key, s => s.Value, StringComparer.OrdinalIgnoreCase); } static void CopySourceFiles() { foreach (var file in _sourceFiles) { Console.WriteLine("\t正在复制:{0}", file.Key); var target = Path.Combine(_destinationDirectory, file.Key).ToLower(); Directory.CreateDirectory(Path.GetDirectoryName(target)); File.Copy(file.Value, target); } Console.WriteLine("复制完成"); Console.WriteLine(); } } }