vb.net脚本
Welcome my friends to the second instalment and follow-up to our
欢迎我的朋友们参加第二期和后续活动
Minify and Concatenate Your Scripts and Stylesheets series. 缩小和连接脚本和样式表系列。In this instalment we will be discussing another similar technique that was previously discussed in the first article.
在本期中,我们将讨论另一篇先前在第一篇文章中讨论过的类似技术。
The previous process discussed a method to do this for your .Net website automatically by parsing the outputted HTML for your stylesheets and scripts, combining them into one string each, saving the strings to server cache and finally rendering them through a gzipped output.
前面的过程讨论了一种方法,该方法通过解析样式表和脚本的输出HTML,将它们组合成一个字符串,将字符串保存到服务器缓存中,最后通过gzip压缩的输出呈现,从而自动为.Net网站执行此操作。
This is all done by utilizing a .Net HttpModule.
所有这些都是通过使用.Net HttpModule完成的。
I thought it would be the perfect solution, but have since found out otherwise. While the methodology involved remains the same, the delivery method is what is changing. I have found that if you utilize Master Pages (or have developed a highly customizable CMS system like myself - shameless plug = check my bio), this method simply does not work.
我认为这将是理想的解决方案,但此后发现了其他方法。 尽管所涉及的方法保持不变,但交付方法正在发生变化。 我发现,如果您利用母版页(或像我一样开发了高度可定制的CMS系统-无耻的插件=检查我的生物),则此方法根本行不通。
Thus, I set myself the task of making something work.
因此,我为自己设定了使某项工作起作用的任务。
What I found was that by using a Response.Filter on the IO.Stream of the HTML rendered, we can accomplish the same goals, without any modifications to your web.config file, and without the use of an HttpModule.
我发现,通过在呈现HTML的IO.Stream上使用Response.Filter,我们可以实现相同的目标,而无需对web.config文件进行任何修改,也无需使用HttpModule。
In my test configuration, I simply have 3 files (default.aspx, default2.aspx, and default3.aspx);
在我的测试配置中,我仅拥有3个文件(default.aspx,default2.aspx和default3.aspx)。
Default.aspx simply is an .Net HTML file with 2 references to 2 seperate stylesheet, 4 references to 4 seperate javascript files, with the filter applied on Page_Load in the code behind
Default.aspx只是一个.Net HTML文件,其中2个引用指向2个单独的样式表,4个引用指向4个单独的javascript文件,并且该过滤器应用于后面代码中的Page_Load
Default2.aspx simply contains a placeholder reference for the Master Page we use for it, which happens to contain the same structure as Default.aspx
Default2.aspx仅包含我们用于其母版页的占位符引用,该引用恰好包含与Default.aspx相同的结构
Default3.aspx is the same file as Default.aspx, except it does not contain the filter in the code behind.
Default3.aspx与Default.aspx是同一文件,不同之处在于它在后面的代码中不包含过滤器。
For testing we used FireFox, and the handy dandy FireBug plugin, and the results were as such:
为了进行测试,我们使用了FireFox和方便的dandy FireBug插件,结果如下:
Default.aspx - Fresh Load - Non-Cached: 3 Requests = 2.25s | Cached: 3 Requests From Cache = 1.67s
Default.aspx-新负载-非缓存:3个请求= 2.25s | 缓存:来自缓存的3个请求= 1.67s
Default2.aspx - Fresh Load - Non-Cached: 3 Requests = 2.15s | Cached: 3 Requests From Cache = 1.77s
Default2.aspx-新负载-非缓存:3个请求= 2.15s | 缓存:来自缓存的3个请求= 1.77s
Default3.aspx - Fresh Load - Non-Cached: 7 Requests = 3.32s | Cached: 7 Requests No Cache = 3.25s
Default3.aspx-新负载-非缓存:7个请求= 3.32s | 已缓存:7个请求,没有缓存= 3.25s
The biggest thing to note here is the number of requests, and keep in mind this testing is done by VS2008's debugging.
这里要注意的最大事情是请求的数量,请记住,此测试是通过VS2008的调试完成的。
Complete File Listing:
完整的文件清单:
/App_Code/Common.vb
/App_Code/Common.vb
/App_Code/DynamicCompressi
/ App_Code / DynamicCompressi
vb/Scripts/custom.js
/脚本/custom.js
/Scripts/jquery_latest.js
/脚本/jquery_latest.js
/Scripts/jquery.pngFix.pac
/Scripts/jquery.pngFix.pac
k.js.js/Scripts/jquery.preloadcss
/脚本/jquery.preloadcss
images.js。 js/Styles/layout1.css
/样式/layout1.css
/Styles/layout2.css
/样式/layout2.css
/Default.aspx w/Code Behind
/Default.aspx w /后面有代码
/Default2.aspx w/Code Behind
/Default2.aspx带代码隐藏
/Default3.aspx w/Code Behind
/Default3.aspx带代码隐藏
/MasterPage.master w/Code Behind
/MasterPage.master(含代码)
/Script.aspx w/o Code Behind
/Script.aspx没有代码隐藏
/Style.aspx w/o Code Behind
/Style.aspx没有代码
/web.config
/web.config
(it should be noted that if you do not have an /App_Code/ directory, you can simply add it to your project, or add the files I list above to your class assembly)
(应注意,如果您没有/ App_Code /目录,则可以将其简单地添加到您的项目中,或者将我上面列出的文件添加到您的类程序集中)
And now, without further ado... the code (I won't bother listing the code for the scripts, stylesheets, or the web.config file... these will be up to you to have handy... or just download the package at the end):
现在,不用费劲了。。。代码(我不会费心列出脚本,样式表或web.config文件的代码...这些将由您方便使用...或下载最后的包装):
/App_Code/Common.vb
/App_Code/Common.vb
Imports Microsoft.VisualBasic
''' <summary>
''' Our common class file
''' </summary>
''' <remarks></remarks>
Public Class Common
''' <summary>
''' Let's cache this stuff
''' </summary>
''' <remarks></remarks>
Public Shared Sub CacheIt()
' Allow the browser to store resposes in the 'History' folder
HttpContext.Current.Response.Cache.SetAllowResponseInBrowserHistory(True)
' Set our cacheability to Public
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public)
' Resource is valid until the expiration has passed
HttpContext.Current.Response.Cache.SetValidUntilExpires(True)
' Set out last modified date to last year
HttpContext.Current.Response.Cache.SetLastModified(Date.Now.AddDays(-366))
' We want to store and cache the resource
HttpContext.Current.Response.AddHeader("Cache-Control", "store, cache")
' Set the Pragma to cache
HttpContext.Current.Response.AddHeader("pragma", "cache")
' Not sure if this one really works, but it doesn't throw an error, and Google likes resources served from a cookie-less domain... eh... worth a shot
HttpContext.Current.Response.AddHeader("Set-Cookie", "false")
' Make sure our cache control is Public
HttpContext.Current.Response.CacheControl = "public" '
'Set the expiration date of the resource until next year
HttpContext.Current.Response.Expires = 24 * 60 * 366
HttpContext.Current.Response.ExpiresAbsolute = DateAdd(DateInterval.Hour, 24 * 366, Date.Now)
End Sub
''' <summary>
''' Let's check to see if the browser accepts GZip encoding
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function IsGZipEnabled() As Boolean
Dim accEncoding As String = HttpContext.Current.Request.Headers("Accept-Encoding")
'Does the browser accept content encoding?
If (Not accEncoding Is Nothing) Then
If (accEncoding.Contains("gzip") Or accEncoding.Contains("deflate")) Then
Return True
Else
Return False
End If
Else
Return False
End If
End Function
End Class
/App_Code/DynamicCompressi
/ App_Code / DynamicCompressi vb
Imports Microsoft.VisualBasic
Imports System.Web
Imports System.Text.RegularExpressions
Imports System.Text
Imports System.IO
Imports System.Web.Caching
Namespace ZipCM
''' <summary>
''' Our compressor class for miniying and concatenating javascript files and css files
''' </summary>
''' <remarks></remarks>
Public Class Compressor
Inherits System.IO.Stream
#Region " Properties "
#Region " Required Properties by the IO.Stream Interface "
Public Overrides ReadOnly Property Length() As Long
Get
End Get
End Property
Public Overrides Property Position() As Long
Get
End Get
Set(ByVal value As Long)
End Set
End Property
Public Overrides ReadOnly Property CanRead() As Boolean
Get
End Get
End Property
Public Overrides ReadOnly Property CanSeek() As Boolean
Get
End Get
End Property
Public Overrides ReadOnly Property CanWrite() As Boolean
Get
End Get
End Property
#End Region
#Region " Internal Properties "
Private HTML As String, FileCSS As String, FileJS As String
Private context As HttpContext
Private PageID As Long
Private Base As System.IO.Stream
#End Region
#End Region
#Region " Methods "
#Region " Public "
#Region " Required by IO.Stream "
Public Overrides Sub Flush()
End Sub
Public Overrides Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer
Return Me.Base.Read(buffer, offset, count)
End Function
Public Overrides Function Seek(ByVal offset As Long, ByVal origin As System.IO.SeekOrigin) As Long
End Function
Public Overrides Sub SetLength(ByVal value As Long)
End Sub
#End Region
''' <summary>
''' Fire up our class passing in our Response Stream
''' </summary>
''' <param name="ResponseStream"></param>
''' <remarks></remarks>
Public Sub New(ByVal ResponseStream As System.IO.Stream)
' Just in case it does not exist, let's throw up ;)
If ResponseStream Is Nothing Then Throw New ArgumentNullException("ResponseStream")
' Set our Response to the IO.Stream
Me.Base = ResponseStream
End Sub
''' <summary>
''' We want to overwrite our default Write method so we can do our stuff
''' </summary>
''' <param name="buffer"></param>
''' <param name="offset"></param>
''' <param name="count"></param>
''' <remarks></remarks>
Public Overrides Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer)
' Get HTML code from the Stream
HTML = System.Text.Encoding.UTF8.GetString(buffer, offset, count)
'Compress and Concatenate the CSS
Me.CompressStyles(HTML)
'Compress and Concatenate the JS
Me.CompressJavascript(HTML)
' Send output, but replace some unneeded stuff first, like tabs, and multi-spaces
HTML = HTML.Replace(vbTab, String.Empty).Replace(" ", String.Empty).Replace(vbCrLf, String.Empty)
' Set the buffer to the Bytes gotten from our HTML
buffer = System.Text.Encoding.UTF8.GetBytes(HTML)
' Write It Out (not unlike 'Spit It Out - Slipknot'
Me.Base.Write(buffer, 0, buffer.Length)
End Sub
#End Region
#Region " Internal "
''' <summary>
''' Compress and Concatenate Our Style Sheets
''' </summary>
''' <param name="StrFile"></param>
''' <remarks></remarks>
Private Sub CompressStyles(ByVal StrFile As String)
Dim FilesM As MatchCollection, FileName As String
' Grab all references to stylesheets as they are in our HTML
FilesM = Regex.Matches(StrFile, "<link.*?href=""(.*?)"".*? />")
Dim M(FilesM.Count - 1) As String
' Now that we have all our files in a match collection,
' we'll loop through each file and put each line together into one string
For i As Long = 0 To M.Length - 1
M(i) = FilesM(i).Groups(1).Value
FileName = HttpContext.Current.Server.MapPath(M(i).Replace("/", "\"))
FileName = FileName.Replace("\\\", "\")
' Make sure the file exists locally, then read the entire thing to add into our string
If File.Exists(FileName) Then
Using objFile As New StreamReader(FileName)
FileCSS += objFile.ReadToEnd
objFile.Close()
End Using
' Now that we have our concatenated string,
' let's replace the unneeded stuff
' Comments
FileCSS = Regex.Replace(FileCSS, "/\*.+?\*/", "", RegexOptions.Singleline)
' 2 Spaces
FileCSS = FileCSS.Replace(" ", String.Empty)
' Carriage Return
FileCSS = FileCSS.Replace(vbCr, String.Empty)
' Line Feed
FileCSS = FileCSS.Replace(vbLf, String.Empty)
' Hard Return
FileCSS = FileCSS.Replace(vbCrLf, String.Empty)
' Tabs w/ a single space
FileCSS = FileCSS.Replace(vbTab, " ")
FileCSS = FileCSS.Replace("\t", String.Empty)
' Get rid of spaces next to the special characters
FileCSS = FileCSS.Replace(" {", "{")
FileCSS = FileCSS.Replace("{ ", "{")
FileCSS = FileCSS.Replace(" }", "}")
FileCSS = FileCSS.Replace("} ", "}")
FileCSS = FileCSS.Replace(" :", ":")
FileCSS = FileCSS.Replace(": ", ":")
FileCSS = FileCSS.Replace(", ", ",")
FileCSS = FileCSS.Replace("; ", ";")
FileCSS = FileCSS.Replace(";}", "}")
' Another comment
FileCSS = Regex.Replace(FileCSS, "/\*[^\*]*\*+([^/\*]*\*+)*/", "$1")
End If
Next
Dim tmpCt As Long = 0
' One more loop to get rid of the multiple references,
' and replace it with a sigle reference to our new and improved stylesheet file
For Each tmpS As Match In FilesM
tmpCt += 1
If tmpCt = FilesM.Count Then
' If our count = the number of matches then replace
StrFile = StrFile.Replace(tmpS.Groups(0).ToString(), "<link type=""text/css"" rel=""stylesheet"" href=""/Style.aspx"" />")
Else
' if not, just get rid of it
StrFile = StrFile.Replace(tmpS.Groups(0).ToString(), String.Empty)
End If
Next
FilesM = Nothing
' Need to put the New and Improved CSS string somewhere!
' What a better place for it, then the server cache
HttpContext.Current.Cache("CSS") = FileCSS
' We also need to return the improved HTML
HTML = StrFile
End Sub
''' <summary>
''' Compress Our Scripts
''' </summary>
''' <param name="StrFile"></param>
''' <remarks></remarks>
Private Sub CompressJavascript(ByVal StrFile As String)
Dim FilesM1 As MatchCollection, FileName As String
' Grab all references to script files as they are in our HTML
FilesM1 = Regex.Matches(StrFile, "<script.*?src=""(.*?)"".*?></script>")
Dim M1(FilesM1.Count - 1) As String
' Now that we have all our files in a match collection,
' we'll loop through each file and put each line together into one string
For j As Long = 0 To M1.Length - 1
M1(j) = FilesM1(j).Groups(1).Value
FileName = HttpContext.Current.Server.MapPath(M1(j).Replace("/", "\"))
FileName = FileName.Replace("\\\", "\")
' Make sure the file exists locally, then read the entire thing to add into our string
If File.Exists(FileName) Then
Using objFile1 As New StreamReader(FileName)
FileJS += objFile1.ReadToEnd
objFile1.Close()
End Using
' Now that we have our concatenated string,
' let's replace the unneeded stuff
' Comments
FileJS = Regex.Replace(FileJS, "(// .*?$)", "", RegexOptions.Multiline)
FileJS = Regex.Replace(FileJS, "(/\*.*?\*/)", "", RegexOptions.Multiline)
' 2 Spaces with 1 Space
FileJS = FileJS.Replace(" ", " ")
' Carriage Return
FileJS = FileJS.Replace(vbCr, vbLf)
' Hard Return w/ Line Feed
FileJS = FileJS.Replace(vbCrLf, vbLf)
' Tabs with a single space
FileJS = FileJS.Replace(vbTab, " ")
End If
Next
Dim tmpCt1 As Long = 0
' One more loop to get rid of the multiple references,
' and replace it with a sigle reference to our new and improved stylesheet file
For Each tmpS As Match In FilesM1
tmpCt1 += 1
If tmpCt1 = FilesM1.Count Then
' If our count = the number of matches then replace
StrFile = StrFile.Replace(tmpS.Groups(0).ToString(), "<script type=""text/javascript"" src=""/Script.aspx""></script>")
Else
' if not, just get rid of it
StrFile = StrFile.Replace(tmpS.Groups(0).ToString(), String.Empty)
End If
Next
FilesM1 = Nothing
' Need to put the New and Improved JS Somewhere!
' What a better place for it then the server cache
HttpContext.Current.Cache("JS") = FileJS
' Also need to return the New and Improved HTML
HTML = StrFile
End Sub
#End Region
#End Region
End Class
End Namespace
/Default.aspx
/Default.aspx
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" Debug="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Untitled Page</title>
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<link href="/styles/layout1.css" rel="stylesheet" type="text/css" />
<link href="/styles/layout2.css" rel="stylesheet" type="text/css" />
</head>
<body>
Just a test to make sure the CSS and JS works
<a href="javascript:;" onclick="alert($('body').html());">Click Here</a>
<script type="text/javascript" src="/scripts/jquery.js"></script>
<script type="text/javascript" src="/scripts/jquery.pngFix.pack.js"></script>
<script type="text/javascript" src="/scripts/jquery.preloadcssimages.js"></script>
<script type="text/javascript" src="/scripts/custom.js"></script>
</body>
</html>
/Default.aspx.vb - The Code-Behind
/Default.aspx.vb-代码隐藏
Partial Class _Default
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Enable GZip Encoding for this page
If Common.IsGZipEnabled() Then
Dim accEncoding As String
accEncoding = Context.Request.Headers("Accept-Encoding")
If accEncoding.Contains("gzip") Then
Response.Filter = New System.IO.Compression.GZipStream(Response.Filter, System.IO.Compression.CompressionMode.Compress)
Response.AppendHeader("Content-Encoding", "gzip")
Else
Response.Filter = New System.IO.Compression.DeflateStream(Response.Filter, System.IO.Compression.CompressionMode.Compress)
Response.AppendHeader("Content-Encoding", "deflate")
End If
End If
'Cache our response in the browser
Common.CacheIt()
'Concatenate and Minify our Scripts and Stylesheets
Response.Filter = New ZipCM.Compressor(Response.Filter)
End Sub
End Class
/Default2.aspx - Blank Code-Behind
/Default2.aspx-隐藏空白代码
<%@ Page Language="VB" MasterPageFile="~/MasterPage.master" AutoEventWireup="false" CodeFile="Default2.aspx.vb" Inherits="Default2" title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
Just a test to make sure the CSS and JS works
<a href="javascript:;" onclick="alert($('body').html());">Click Here</a>
</asp:Content>
/Default3.aspx - Blank Code-Behind, contains the exact same code as /Default.aspx, except without the Code-Behind Processing
/Default3.aspx-空白代码隐藏,包含与/Default.aspx完全相同的代码,除了没有代码隐藏处理
/MasterPage.master
/MasterPage.master
<%@ Master Language="VB" CodeFile="MasterPage.master.vb" Inherits="MasterPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Untitled Page</title>
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<link href="/styles/layout1.css" rel="stylesheet" type="text/css" />
<link href="/styles/layout2.css" rel="stylesheet" type="text/css" />
</head>
<body>
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
<script type="text/javascript" src="/scripts/jquery.js"></script>
<script type="text/javascript" src="/scripts/jquery.pngFix.pack.js"></script>
<script type="text/javascript" src="/scripts/jquery.preloadcssimages.js"></script>
<script type="text/javascript" src="/scripts/custom.js"></script>
</body>
</html>
/MasterPage.master.vb
/MasterPage.master.vb
Partial Class MasterPage
Inherits System.Web.UI.MasterPage
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Enable GZip Encoding for this page
If Common.IsGZipEnabled() Then
Dim accEncoding As String
accEncoding = Context.Request.Headers("Accept-Encoding")
If accEncoding.Contains("gzip") Then
Response.Filter = New System.IO.Compression.GZipStream(Response.Filter, System.IO.Compression.CompressionMode.Compress)
Response.AppendHeader("Content-Encoding", "gzip")
Else
Response.Filter = New System.IO.Compression.DeflateStream(Response.Filter, System.IO.Compression.CompressionMode.Compress)
Response.AppendHeader("Content-Encoding", "deflate")
End If
End If
'Cache our response in the browser
Common.CacheIt()
'Concatenate and Minify our Scripts and Stylesheets
Response.Filter = New ZipCM.Compressor(Response.Filter)
End Sub
End Class
/Script.aspx
/Script.aspx
<%@ Page Language="VB" %>
<%
'Enable GZip Encoding
If Common.IsGZipEnabled() Then
Dim accEncoding As String
accEncoding = Context.Request.Headers("Accept-Encoding")
If accEncoding.Contains("gzip") Then
Context.Response.Filter = New System.IO.Compression.GZipStream(Context.Response.Filter, System.IO.Compression.CompressionMode.Compress)
Context.Response.AppendHeader("Content-Encoding", "gzip")
Else
Context.Response.Filter = New System.IO.Compression.DeflateStream(Context.Response.Filter, System.IO.Compression.CompressionMode.Compress)
Context.Response.AppendHeader("Content-Encoding", "deflate")
End If
End If
'Cache our response in the browser
Common.CacheIt()
'Set the content type to output true javascript
Response.ContentType = "text/javascript"
If Not (Cache("JS") Is Nothing) Then
'write out the javascript string from our cache
Response.Write(Cache("JS").ToString())
End If
%>
/Style.aspx
/Style.aspx
<%@ Page Language="VB" %>
<%
'Enable GZip Encoding
If Common.IsGZipEnabled() Then
Dim accEncoding As String
accEncoding = Context.Request.Headers("Accept-Encoding")
If accEncoding.Contains("gzip") Then
Context.Response.Filter = New System.IO.Compression.GZipStream(Context.Response.Filter, System.IO.Compression.CompressionMode.Compress)
Context.Response.AppendHeader("Content-Encoding", "gzip")
Else
Context.Response.Filter = New System.IO.Compression.DeflateStream(Context.Response.Filter, System.IO.Compression.CompressionMode.Compress)
Context.Response.AppendHeader("Content-Encoding", "deflate")
End If
End If
'Cache our response in the browser
Common.CacheIt()
'Set the content type to output true CSS
Response.ContentType = "text/css"
If Not (Cache("CSS") Is Nothing) Then
'Write it out from the cache
Response.Write(Cache("CSS").ToString())
End If
%>
Here's the complete un-compiled project
这是完整的未编译项目
make sure you run this in a dev environment so you can tweak it to your needs before deploying.
确保在开发环境中运行此程序,以便在部署之前可以对其进行调整。
Test-Project.zip Test-Project.zipvb.net脚本