65.9K
CodeProject 正在变化。 阅读更多。
Home

Web 应用程序的 JavaScript 压缩工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (24投票s)

2003年7月25日

CPOL

18分钟阅读

viewsIcon

345522

downloadIcon

3357

一个压缩 JavaScript 文件以减小其大小并提高页面加载时间的工具。

引言

本文介绍了一个 JavaScript 压缩工具,该工具可获取您的 JavaScript 源代码,通过删除所有注释、多余的空格,以及可选地尽可能多地删除换行符,并可选地缩短函数参数和变量名来对其进行压缩。这将减小脚本大小,并可能有助于您的页面加载更快,减少带宽消耗。当启用换行符删除和变量名压缩时,一个微小的副作用是它提供了轻量级的代码混淆,使得普通用户更难阅读和/或进行篡改。它不会阻止有决心的人重新格式化和反向工程,但这并不是该工具的意图。

我开发此工具是为了在我自己的 ASP.NET 项目中使用。代码是用 C# 编写的,但只要您安装了 .NET Framework,它就可以用来压缩任何 Web 项目(.NET 或其他)的 JavaScript。提供的项目文件适用于 Visual Studio 2003,但也可以在 Visual Studio 2005 下打开、转换并成功编译。

有三个级别的压缩

  • 不删除换行符

    脚本中不会删除换行符(除了被视为多余的,例如空行上的换行符)。只删除注释和多余的空格。此模式提供良好的压缩,并确保代码不会中断。

  • 尽可能删除换行符

    在此模式下,只要被认为安全,就会从语句末尾删除换行符,通常可额外压缩 2% 到 5%。例如,以操作符(如 */+- 等)结尾的行,以及以分号结尾的行,其末尾的换行符将被删除。还有其他几种条件可以满足,从而导致删除,这些条件将在下面的代码描述部分中进行说明。还会采取措施防止在诸如缺少分号等情况下删除,以免中断代码。但是,我可能没有捕捉到所有这些情况,所以如果此模式破坏了代码,您可以回退到上面的模式。当您勤奋地在所有可以使用的语句后面加上分号以正确标记其终结点时,此模式可获得最佳效果。

  • 函数参数和变量名压缩

    这可以与前两种压缩选项之一结合使用,以进一步减小脚本大小。启用后,尽可能多地重命名和缩短函数参数和变量名。命名方案以字母 az 开始,然后是 _a_z_aa_az_ba_bz 等。启用此选项后,脚本大小通常可以额外减小 10% 到 15%。此选项可能存在代码中断的更高风险,因此默认情况下不启用。如果启用,建议在部署所有压缩后的脚本之前对其进行彻底测试。

代码块也可以用特殊的 // #pragma NoCompStart// #pragma NoCompEnd 注释包围,以将某些部分排除在压缩之外。这对于在压缩的脚本文件中包含标题中的版权声明或跳过正在测试的部分很有用。例如

// #pragma NoCompStart
//====================================================
// File    : TestScript1.js
// Author  : Eric Woodruff
// Updated : 07/23/2003
// #pragma NoCompEnd

// Anything from this point forward will be compressed
// .
// .
// .

// #pragma NoCompStart

// Skip compression on this section
function Test()
{
    return true;
}

// #pragma NoCompEnd

// Resume compression
// .
// .
// .

#pragma 注释应单独占一行,并且在最终压缩的脚本中将被删除。与 #pragma 在同一行上的任何尾随注释文本都将被忽略,并且也将被删除。压缩器也不关心 #pragma 语句上的空格或大小写。

程序

提供了两个版本的程序。第一个是交互式版本,您可以使用它来测试不同的压缩模式。它是一个用 C# 编写的 Windows Forms 应用程序。运行后,只需将您的 JavaScript 代码粘贴到“原始脚本”文本框中,打开或关闭“换行符删除”和“变量名压缩”选项,然后单击“压缩”按钮。然后,压缩后的脚本将显示在“压缩脚本”文本框中,其下方会显示一些压缩统计信息。文本可以从“压缩脚本”文本框复制到剪贴板。

请注意,在使用“仅测试变量名压缩”选项时,脚本代码不会被压缩。只压缩参数和变量名。这可能有助于定位变量名压缩代码中的问题。尽管脚本代码未被压缩,但注释会被删除,以便命名结果匹配(即,它不会因为匹配注释中的单词(如“a”、“be”或“to”)而使用不同的名称)。

第二个也是最有用的工具是压缩器的控制台模式版本,它可以作为 ASP.NET 项目的预构建步骤的命令,以压缩项目中的脚本。它还可以用于压缩存储在自定义 Web 控件中的脚本作为嵌入资源。命令行语法如下所示。选项和文件规范不区分大小写,并按遇到的顺序从左到右处理。

JSCompressCL [/options] filespec [[/options]
filespec ...]

可用命令行选项如下

选项 描述
/? 显示帮助
/q 安静模式。不显示压缩统计信息。
/debug 调试构建,压缩被抑制,脚本被原样传递到输出文件夹,以便于调试。可以使用 /f 选项强制压缩。
/release 发布构建,启用压缩(如果未指定构建选项,则为默认值)。
/k (保留) 除非换行符是多余的(例如,空行),否则不删除换行符。
/d (删除) 尽可能删除换行符(如果未指定换行符删除选项,则为默认值)。
/v 压缩变量和参数名称。
/t 仅压缩变量名(用于测试)。这也会删除注释,但所有其他压缩选项将被忽略。
/f 在调试构建中强制压缩已处理的文件。对于在调试构建中测试压缩脚本很有用。
/r 递归扫描文件规范中的子文件夹。子文件夹结构将在输出文件夹中重复。
/o:<目录> 指定输出目录(如果未指定,则为当前目录)。
filespec 一个或多个要压缩的文件,接受通配符。

调试和发布构建选项已详细说明,以便于使用 IDE 宏在项目的预构建步骤中指定它们。下面将对此进行说明。

至少,您应该指定一个与要压缩脚本所在的目录不同的输出目录。例如,您可能希望将未压缩的脚本存储在名为“ScriptsDev”的文件夹中,并告诉压缩器将压缩后的脚本存储在应用程序运行时使用的名为“Scripts”的文件夹中。压缩器不会覆盖源脚本。在调试构建中,它还会检查脚本的现有副本,如果时间戳大于或等于源脚本,则会跳过它。这样可以避免在调试期间每次构建项目时都重新创建未更改的脚本文件。在这种情况下会显示“最新”消息。在发布构建中,脚本始终会经过处理,以确保它们是最新的并已压缩。

如果脚本被压缩,该工具将显示源文件名和目标文件名以及压缩统计信息。可以使用 /q 命令行选项将其关闭。下面是一些示例(行已换行以便显示)

Implied release build with line feed removal,
no stats displayed.

    JSCompressCL /q /o:\MyProj\Scripts
        \MyProj\ScriptsDev\*.js

Explicit release build with line feed removal,
stats are displayed.

    JSCompressCL /release /o:\MyProj\Scripts
        \MyProj\ScriptsDev\*.js

Line feed removal disabled for first file set, line feed
removal and variable name compression enabled for second file set.

    JSCompressCL /o:\MyProj\Scripts
        /k \MyProj\ScriptsDev1\*.js
        /d /v \MyProj\ScriptsDev2\*.js

Debug build, no compression. Scripts are passed
through unmodified for debugging purposes.

    JSCompressCL /Debug /o:\MyProj\Scripts
        \MyProj\ScriptsDev\*.js

Debug build with forced compression. Scripts are
compressed even though it's a debug build.

    JSCompressCL /Debug /f /o:\MyProj\Scripts
        \MyProj\ScriptsDev\*.js

将控制台版本用作项目的预构建步骤

将应用程序的控制台版本复制到您 PC 上的某个文件夹中。要将控制台版本用作 Web 项目的预构建步骤,请创建一个文件夹来包含未压缩的脚本(例如,ScriptDev),以及另一个文件夹来包含应用程序运行时使用的压缩脚本(例如,Scripts)。要在项目中创建新文件夹,请右键单击项目名称,选择“添加...”,选择“新建文件夹”,然后输入文件夹名称。通过右键单击该文件夹并选择“添加...”然后选择“添加新项...”来创建新项,或者如果已将现有文件复制到新文件夹,则选择“添加现有项...”来添加新脚本。将其添加到项目文件夹后,右键单击脚本,然后选择“属性”。对于开发(未压缩)文件夹中的脚本,将“生成操作”属性从“内容”更改为“”。您可以选择添加压缩文件夹中脚本的副本,并将其生成操作设置为“内容”。

下一步是右键单击项目名称,选择“属性”,展开“通用属性”文件夹,然后选择“生成事件”子项。单击“预构建事件命令行”选项以输入要运行的命令行。您可以单击 **“...”** 按钮打开一个对话框,其中包含一个更大的编辑器和可用宏列表。下面是一个常用命令行的示例(行已换行以便显示)。将工具的路径替换为您在 PC 上存储它的路径。

D:\Utils\JSCompressCL /$(ConfigurationName)
    /o:$(ProjectDir)Scripts $(ProjectDir)ScriptsDev\*.js

/$(ConfigurationName) 选项将扩展为构建时生效的配置名称。假设使用默认值,这相当于 /Debug/Release,从而在调试构建中关闭压缩,以便您可以测试和调试脚本,并在发布构建中启用压缩。请注意,命令行处理器会查找以“Debug”或“Release”开头的条目,因此您可以使用自定义配置名称。只要它们以这两个关键字之一开头,它就会选择相应的构建类型。如果配置名称包含空格,请将选项放在引号中。如前所述,在调试构建中,脚本会按原样传递到目标文件夹,以便于调试。如果您希望在调试构建中压缩脚本,请添加 /f 命令行选项以强制使用压缩。

/o:$(ProjectDir)Scripts 选项相当于压缩脚本文件夹。对于我的项目,它始终是主项目文件夹的子文件夹,因此使用了 `$(ProjectDir)` 宏。请根据您的项目相应地修改路径名称。

同样适用于 `$(ProjectDir)ScriptsDev\*.js` 选项,该选项告诉工具在哪里查找需要压缩的脚本。如上所述,请根据您的项目相应地修改路径名称。

压缩作为嵌入资源的脚本

如果您正在开发一个 Web 控件,例如,它使用包含在程序集中的脚本作为嵌入资源,您仍然可以使用上述步骤来压缩它们。唯一的区别是,在设置文件夹时,如上所述,制作脚本的初始副本,并将其放在压缩脚本文件夹中。在项目管理器中,右键单击压缩脚本文件夹中的脚本,选择“属性”,然后将“生成操作”属性更改为“嵌入资源”。构建项目时,预构建命令将压缩脚本,然后项目将按正常方式构建,压缩后的脚本将被嵌入为程序集中的资源。

代码工作原理

Windows Forms 和控制台应用程序的代码相当简单,没什么特别要描述的。Forms 版本从控件中获取数据,并将其与 JSCompressor 类一起使用。控制台模式版本执行相同操作,但使用命令行参数。类本身是发生动作的地方,下面将对此进行描述。类代码可在 JSCompressor.cs 文件中找到。

基本信息

JSCompressor 类相当简单,包含几个构造函数、用于修改换行符删除模式和变量名压缩设置的属性、用于压缩脚本的公共方法以及几个私有数据成员和方法。默认构造函数默认启用换行符删除。构造函数的第二个版本接受一个布尔参数,允许您指定换行符删除的初始状态(true 表示启用,false 表示禁用)。LineFeedRemoval 属性允许您在构造后修改模式。第三个构造函数接受两个布尔参数,允许您指定换行符删除和变量名压缩选项的初始状态。CompressVariableNames 属性可用于在构造后修改变量名压缩设置。默认情况下,变量名压缩是关闭的。此外,可以将 TestVariableNameCompression 属性设置为 true 以测试变量名压缩代码。设置为 true 时,脚本压缩被禁用,并且仅压缩参数和变量名。如上所述,注释也会被删除,以便您获得一组重命名变量和参数相同的集合。

压缩过程

JSCompressor 类的 Compress 方法执行所有工作。它接收未压缩脚本的副本,并返回压缩后的版本。

/// <summary>
/// Compress the specified JavaScript code.
/// </summary>
/// <param name="strScript">The script to compress</param>
/// <returns>The compressed script</returns>
public string Compress(string strScript)
{
    string strCompressed;
    char [] achScriptChars;

    // Don't bother if there is nothing to compress
    if(strScript == null || strScript.Length == 0)
        return strScript;

    // Set up for compression
    scLiterals.Clear();
    scNoComps.Clear();

    // Create the regular expressions and match evaluators on
    // first use.
    if(reInsLit == null)
    {
        reExtNoComp = new Regex(@"//\s*#pragma\s*NoCompStart.*?" +
            @"//\s*#pragma\s*NoCompEnd.*?\n",
            RegexOptions.Multiline | RegexOptions.Singleline |
            RegexOptions.IgnoreCase);
        reDelNoComp = new Regex(@"//\s*#pragma\s*NoComp(Start|End).*\n",
            RegexOptions.Multiline | RegexOptions.IgnoreCase);
        reInsLit = new Regex("\xFE|\xFF");
        meInsLit = new MatchEvaluator(OnMarkerFound);
        meExtNoComp = new MatchEvaluator(OnNoCompFound);

        reFuncParams = new Regex(@"function.*?\((.*?)\)(.*?|\n)?\{",
            RegexOptions.IgnoreCase | RegexOptions.Singleline);
        reFindVars = new Regex(@"(var\s+.*?)(;|$)",
            RegexOptions.IgnoreCase | RegexOptions.Multiline);
        reStripVarPrefix = new Regex(@"^var\s+",
            RegexOptions.IgnoreCase);
        reStripParens = new Regex(@"\(.*?,.*?\)|\[.*?,.*?\]",
            RegexOptions.IgnoreCase);
        reStripAssign = new Regex(@"(=.*?)(,|;|$)",
            RegexOptions.IgnoreCase);
    }

第一部分初始化两个字符串集合,它们将最终包含通过 #pragma 注释指定的任何“无压缩”部分以及在解析过程中找到的任何文字字符串。还初始化了一组正则表达式和匹配评估器,以帮助进行解析和压缩过程。它们的使用将在后面描述。

// Extract sections that the user doesn't want compressed
// and replace them with a marker.
strCompressed = reExtNoComp.Replace(strScript, meExtNoComp);

// This is the match evaluator referenced by meExtNoComp:

// Extract the sections that the user doesn't want compressed
// and save them for reinsertion at the end without the #pragmas.
// They are replaced with a marker character.
private string OnNoCompFound(Match match)
{
    scNoComps.Add(reDelNoComp.Replace(match.Value, String.Empty));
    return "\xFE";
}

下一部分提取用户不想压缩的部分(如果有),如 #pragma 注释所指定(即文件顶部的版权声明)。为此,使用一个匹配评估器,该评估器将找到的部分添加到字符串集合中,并用标记字符 (\xFE) 替换脚本中的该部分。标记将在过程结束时替换为未压缩的部分。用标记替换部分有助于其余代码删除多余的空格,因为它需要处理的内容更少。在将找到的部分存储到集合中之前,会从这些部分中剥离 #pragma 注释。

// Split the string into an array for parsing
achScriptChars = strCompressed.ToCharArray();

// Remove comments and extract literals
CompressArray(achScriptChars);

在删除“无压缩”部分后,脚本被分割成字符数组,以便于解析。该数组被传递到 CompressArray 方法,该方法逐个字符扫描脚本,查找块注释、行注释、文字字符串以及用斜杠 enclosed 的 JavaScript 正则表达式(/ /)。通过将注释中的所有字符设置为 null 来删除块注释和行注释。但是,/*@@*/ 之间的部分会保留在代码中,因为它们表示条件编译部分。条件编译标记之间的代码仍会被压缩。请注意,如果使用条件编译注释,请务必在块前面的行末加上分号,因为浏览器不会处理条件块,除非它从一个单独的行开始。

文字字符串和正则表达式被提取并存储在字符串集合中,并使用类似于提取和存储“无压缩”部分的方法替换为标记字符 (\xFF)。同样,这有助于最后一步删除多余的空格,因为它需要处理的内容更少。在此过程中,回车符被转换为换行符,这使得以后可以轻松删除它们。

// Gather up what's left and remove the nulls
strCompressed = new String(achScriptChars);
strCompressed = strCompressed.Replace("\0", String.Empty);

// Skip code compression?
if(!varCompTest)
{
    // Remove all leading and trailing whitespace and condense runs
    // of two or more whitespace characters to just one.
    strCompressed = Regex.Replace(strCompressed, @"^[\s]+|[ \f\r\t\v]+$",
        String.Empty, RegexOptions.Multiline);
    strCompressed = Regex.Replace(strCompressed, @"([\s]){2,}", "$1");

一旦数组被解析,它就会被转换回字符串,并且所有 null 字符(代表已删除的部分)都会被删除。之后,使用正则表达式删除所有行开头的和结尾的空格,并将两个或多个空格字符的连续组合压缩为单个空格。如果仅测试变量名压缩,则此部分和后续步骤将被跳过。

// Line feed removal requested?
if(removeLineFeeds)
{
    // Remove line feeds when they appear near numbers with signs
    // or operators. A space is used between + and - occurrences
    // in case they are increment/decrement operators followed by
    // an add/subtract operation. In other cases, line feeds are
    // only removed following a + or - if it is not part of an
    // increment or decrement operation.
    strCompressed = Regex.Replace(strCompressed, @"([+-])\n\1",
        "$1 $1");
    strCompressed = Regex.Replace(strCompressed, @"([^+-][+-])\n",
        "$1");
    strCompressed = Regex.Replace(strCompressed,
        @"([\xFE{}([,<>/*%&|^!~?:=.;])\n", "$1");
    strCompressed = Regex.Replace(strCompressed,
        @"\n([{}()[\],<>/*%&|^!~?:=.;+-])" ,"$1");
}

下一步是检查是否请求了换行符删除。如果是,则删除数字(带符号)和运算符附近的换行符。如注释中所述,在 +- 字符周围会特别小心,以便在需要时保留自增和自减操作(++--)周围的空格和换行符,以防止代码中断。

// Strip all unnecessary whitespace around operators
strCompressed = Regex.Replace(strCompressed,
 @"[ \f\r\t\v]?([\n\xFE\xFF/{}()[\];,<>*%&|^!~?:=])[ \f\r\t\v]?",
 "$1");
strCompressed = Regex.Replace(strCompressed, @"([^+]) ?(\+)", "$1$2");
strCompressed = Regex.Replace(strCompressed, @"(\+) ?([^+])", "$1$2");
strCompressed = Regex.Replace(strCompressed, @"([^-]) ?(\-)", "$1$2");
strCompressed = Regex.Replace(strCompressed, @"(\-) ?([^-])", "$1$2");

最后一组正则表达式用于清除运算符和标记字符周围的空格。同样,会特别小心 +- 操作符,以正确清除增量和减量操作周围的空格。

// Try for some additional line feed removal savings by
// stripping them out from around one-line if, while,
// and for statements and cases where any of those
// statements immediately follow another.
if(removeLineFeeds)
{
    strCompressed = Regex.Replace(strCompressed,
        @"(\W(if|while|for)\([^{]*?\))\n", "$1");
    strCompressed = Regex.Replace(strCompressed,
        @"(\W(if|while|for)\([^{]*?\))((if|while|for)\([^{]*?\))\n",
        "$1$3");
    strCompressed = Regex.Replace(strCompressed,
        @"([;}]else)\n", "$1 ");
}

在删除所有多余的空格后,如果请求了换行符删除,将执行一些额外的步骤来删除 ifwhilefor 语句周围的不必要换行符。这有助于删除那些语句以任何组合形式出现,且中间没有花括号的实例中的换行符。例如,以下内容将被压缩成一行

if(a == 1)
    for(b = 0; b < 10; b++)
        while(!c)
            c = DoSomething();

如果代码在所有需要分号来标记其终结点的语句上都包含分号,则上述过程通常可以删除脚本中的所有换行符,将其减少为一个长字符流,从而实现最大的代码压缩。

    // Compress variable names too if requested
    if(compressVarNames || varCompTest)
        strCompressed = CompressVariables(strCompressed);

    // Put back the literals and uncompressed sections removed
    // during the parsing step.
    noCompCount = literalCount = 0;
    strCompressed = reInsLit.Replace(strCompressed, meInsLit);

    return strCompressed;
}

// This is the match evaluator referenced by meInsLit:

// Replace a literal or uncompressed section marker with the
// next entry from the appropriate collection.
private string OnMarkerFound(Match match)
{
    if(match.Value == "\xFE")
        return scNoComps[noCompCount++];

    return scLiterals[literalCount++];
}

接下来,如果请求了,将执行变量名压缩。下一节将对此过程进行描述。最后一步是重新插入未压缩的部分和文字字符串。与提取类似,使用正则表达式和匹配评估器。两个私有计数器用于跟踪字符串集合的进度。每找到一个标记字符,就会调用匹配评估器,根据找到的标记,它会从相应的集合中返回下一个元素,然后替换该标记。匹配计数器也会递增,为下一次匹配做好准备。插入完成后,将压缩后的脚本返回给调用者。

参数和变量名压缩

CompressVariables 方法负责压缩函数参数和变量名。由于存在破坏代码的可能性,压缩方法在定位和重命名变量时采取了谨慎的方法。

  • 函数声明括号内的函数参数名称包括在压缩范围内。
  • var 语句同一行上的变量名包括在压缩范围内。但是,如果 var 语句跨越多行并且禁用了额外的换行符删除,则可能会遗漏某些名称。例如
    var string1, string2, num1, num2;

    在上面的示例中,string1string2 总是会被包含,但如果 LineFeedRemoval 属性设置为 false,则 num1num2 不会被包含,因为它们总是出现在单独的行上,没有任何指示表明它们是变量。

  • 类似地,在代码中出现但没有用 var 语句正式声明的变量名将始终被忽略(即,在其他模块中声明的全局变量)。
  • 如果您声明了在其他脚本文件中引用的全局变量,您应该将它们的声明包装在 #pragma NoCompStart/NoCompEnd 部分中,以免它们在声明它们的文件中被重命名。

实际重命名过程如下

private string CompressVariables(string script)
{
    StringCollection scVariables = new StringCollection();
    string[] varNames;
    string name = null, matchName;
    bool incVarName;

    // Find function parameters
    MatchCollection matches = reFuncParams.Matches(script);

    foreach(Match m in matches)
    {
        varNames = m.Groups[1].Value.Split(',');

        // Add each unique name to the list
        foreach(string s in varNames)
        {
            name = s.Trim();

            if(name.Length != 0 && !scVariables.Contains(name))
                scVariables.Add(name);
        }
    }

第一部分使用之前创建的正则表达式搜索函数参数。参数列表被分开,每个唯一的参数名都被添加到变量名字符串集合中。

// Find variable declarations
matches = reFindVars.Matches(script);

foreach(Match m in matches)
{
    // Remove the "var " declaration prefix
    name = reStripVarPrefix.Replace(m.Groups[1].Value, String.Empty);

    // Strip brackets and parentheses containing commas such
    // as array declarations and method calls with parameters.
    name = reStripParens.Replace(name, String.Empty);

    // Remove assignment operations
    name = reStripAssign.Replace(name, "$2");

    varNames = name.Split(',');

    // Add each unique name to the list
    foreach(string s in varNames)
    {
        name = s.Trim();

        if(name.Length != 0 && !scVariables.Contains(name))
            scVariables.Add(name);
    }
}

下一部分使用之前创建的正则表达式搜索包含变量名声明的 var 语句。此步骤稍微复杂一些,因为它必须考虑语句内的赋值以及可能导致错误分割的数组索引引用。例如

var num1, string1 = "Test", num2 = array1[3, 0];
var resultString = functionCall("A", "B");

从语句中删除 var 前缀,然后删除包含括号或圆括号(包含逗号)的表达式的任何部分(如上面示例所示的多维数组索引、函数调用参数等)。一旦它们被删除,将使用最终的正则表达式删除从等号到下一个逗号或行尾的任何剩余赋值文本。完成后,就可以安全地在每个逗号处分割字符串并将唯一名称添加到变量名字符串集合中。

// Replace each variable in the list with a shorter name.
// Start with "a" through "z" then use "_a" through "_z",
// "_aa" to "_az", "_ba" to "_bz", etc.
newVarName = new char[10];
newVarName[0] = '\x60';
varNamePos = 0;
incVarName = true;

foreach(string replaceName in scVariables)
{
    // Increment the variable name and make sure it isn't
    // in use already.
    if(incVarName)
    {
        do
        {
            IncrementVariableName();

            name = new String(newVarName, 0, varNamePos + 1);
            matchName = @"\W" + name + @"\W";

        } while(Regex.IsMatch(script, matchName));

        incVarName = false;
    }

    // Don't bother if the existing name is shorter. This check
    // could be removed to obfuscate the variable name even if it
    // would be longer.
    if(name.Length < replaceName.Length)
    {
        incVarName = true;
        script = Regex.Replace(script,
            @"(\W)" + replaceName + @"(?=\W)", "$1" + name);
    }
}

return script;

最后一步遍历找到的每个唯一变量名,并替换为较短的名称。完成后,将返回压缩后的脚本。如注释中所述,命名方案以 az 开始,如果用完,则添加下划线前缀并继续(_a_z)。下划线确保在超出单个字母变量名后,它不会意外地创建与关键字匹配的名称。如果这些名称用完,它将开始附加字母并遍历每个集合,从 _aa_az_ba_bz 等。代码的编写方式可以进一步扩展名称,但脚本更有可能具有比压缩器生成的唯一名称数量更少的唯一变量。

每创建一个新名称,都会进行检查以确保它在脚本中尚不存在。例如,常见的循环变量名(如 ij)会导致在脚本中已使用的情况下跳过这些新名称。同样,如果新名称比现有名称长,则不会被替换。但是,如前所述,您可以删除该检查以根据需要完全混淆名称。

结论

平均而言,我自己的脚本大小减小了 50% 到 60%。添加变量名压缩可额外节省 10% 到 15% 的平均脚本。当然,您对 JavaScript 代码注释越多,使用缩进使代码更易读,并使用描述性变量名,压缩率就越高,因为有更多的东西可以删除。使用分号标记语句终结点还可以提高压缩率,因为它允许代码删除几乎所有换行符。

历史

06/26/2006 修改了压缩代码以支持条件编译块(/*@ @*/)。修改了命令行压缩器以扫描和压缩子文件夹,如果指定了 ** /r ** 选项。
03/05/2006 添加了压缩函数参数和变量名的选项。在 Visual Studio 2005 和 .NET 2.0 下测试了代码。演示项目是 Visual Studio 2003 项目,但在 Visual Studio 2005 下转换和构建没有任何问题。
07/25/2003 初始发布。
Web 应用程序的 JavaScript 压缩工具 - CodeProject - 代码之家
© . All rights reserved.