合并脚本和样式表
Today is the age of broadband. More and more people are going this route determined to experience the web and it’s multitude of services as quickly and painlessly as possible. Coupled with the move to broadband, people are experiencing the web via their mobile devices – and it is this which most developers/designers forget.
今天是宽带时代。 越来越多的人决定沿着这种方式体验网络,并尽可能快速,轻松地体验各种服务。 伴随着向宽带的迁移,人们正在通过他们的移动设备体验网络-而这正是大多数开发人员/设计人员所忘记的。
With mobile devices becoming mainstream, it is more important than ever that we as designers and developers convey the importance of optimized web sites to our clients. With this in mind, I would like to introduce some strategies I use when developing a website, or web application.
随着移动设备成为主流,我们作为设计师和开发人员向客户传达优化网站的重要性比以往任何时候都更为重要。 考虑到这一点,我想介绍一些在开发网站或Web应用程序时使用的策略。
#1 Make sure no matter how many CSS stylesheets you use, they are placed in between your <head> tags. This will make sure that your styling gets rendered by the browser in a timely manner relative to the rendering of the page.
#1确保无论使用多少CSS样式表,它们都放置在<head>标记之间。 这将确保您的样式相对于页面的呈现被浏览器及时呈现。
#2 Wherever possible, combine your stylesheets into one. This will decrease page load time by cutting down the parrellel downloads the browser has to make to request your page. If you absolutely must have mutliple stylesheets, see below for a code snippet that will do this on the fly for you. (written in VB.NET)
#2尽可能将样式表合并为一个。 通过减少浏览器请求您的页面所需的并行下载,这将减少页面加载时间。 如果您绝对必须有多个样式表,请参见下面的代码段,这些代码段将为您快速完成。 (用VB.NET编写)
#3 We all love javascript, I personally am a big fan of jQuery, and the jQuery UI. Make sure that no matter how many javascript includes you have, they are placed at the very bottom of the page right before the </body> tag. This will make sure the page is rendered before any javascript can interupt the document.
#3我们都喜欢javascript,我个人是jQuery和jQuery UI的忠实拥护者。 确保无论您包含多少个javascript,它们都位于页面</ body>标记之前的最底部。 这将确保在任何javascript可以插入文档之前,已呈现页面。
#4 Wherever possible, combine your script files into one. This will decrease page load time by cutting down on the parrellel downloads the browser has to make to request your page. If you absolutely must have mutliple script files, see below for a code snippet that will do this on the fly for you. (written in VB.NET)
#4尽可能将脚本文件合并为一个。 通过减少浏览器请求页面所需的并行下载,这将减少页面加载时间。 如果您绝对必须具有多个脚本文件,请参见下面的代码段,这些代码段将为您快速完成。 (用VB.NET编写)
#5 Minify both your scripts and stylesheets to achieve optimal download times. This will help your pages load faster – less size, less time to render the page.
#5最小化脚本和样式表,以达到最佳下载时间。 这将帮助您的页面加载更快–尺寸更小,呈现页面的时间更少。
#6 Enable GZip encoding. This will further compress your files making for a faster loading page.
#6启用GZip编码。 这将进一步压缩文件,从而加快页面加载速度。
#7 Wherever possible: Cache, Cache, Cache. I can't say it enough to emphasize how important it is to leverage both server and client caching. This will reduce the number of times your browser has to hit the server for a page request.
#7只要有可能:缓存,缓存,缓存。 我不能说足够多来强调利用服务器和客户端缓存的重要性。 这将减少您的浏览器必须点击服务器才能进行页面请求的次数。
Now without further ado, is some handy, helpful code that will take your page output, parse it for script and css linked stylesheets, combine them into one "file" each, "minify" them, "cache" them and then render them to the browser.
现在,事不宜迟,这里有一些方便,有用的代码,它们将带您的页面输出,将其解析为脚本和CSS链接的样式表,将它们分别组合为一个“文件”,“最小化”它们,“缓存”它们,然后将它们呈现为浏览器。
To be honest, there may be better methods to do this minification, however, in my 'production' environment, what I have listed below is what has worked best for me.
老实说,可能有更好的方法来实现这种最小化,但是,在我的“生产”环境中,下面列出的是最适合我的方法。
This main processing of this project contains five files, and for testing purposes I added four javascript files, two stylesheets, and a default page to test with.
该项目的主要处理过程包含五个文件,出于测试目的,我添加了四个javascript文件,两个样式表和一个默认页面进行测试。
First things first, let's get the common stuff out of the way. I have aptly named this Common.vb and this should be placed in your 'App_Code' directory:
首先,让我们摆脱常见的问题。 我已恰当地命名了此Common.vb,并将其放置在您的“ App_Code”目录中:
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
Now let's get down to business. This is the code that will do the concatenation and minification. Place it in your 'App_Code' folder. Yes folks, it is an HttpModule:
现在让我们开始做生意。 这是将进行串联和最小化的代码。 将其放在您的“ App_Code”文件夹中。 是的,这是一个HttpModule:
Imports System.IO.Compression
Imports System.Web
Imports System.Text.RegularExpressions
Imports System.Text
Imports System.IO
Imports System.Web.Compilation
''' <summary>
''' Dynamic compression mechanism for some of the sites content
''' </summary>
''' <remarks></remarks>
Public Class DynamicCompression
Implements IHttpModule
''' <summary>
''' Initialize our module
''' </summary>
''' <param name="application"></param>
''' <remarks></remarks>
Public Sub Init(ByVal application As HttpApplication) _
Implements IHttpModule.Init
'Add the BeginRequest Handler
AddHandler application.BeginRequest, _
AddressOf Me.Application_BeginRequest
Dim context As HttpContext = application.Context
'Let's see if the browser accepts GZip encoding, if it does add the header to the response
If Common.IsGZipEnabled() Then
Dim accEncoding As String = context.Request.Headers("Accept-Encoding")
If accEncoding.Contains("gzip") Then
context.Response.Filter = New GZipStream(context.Response.Filter, CompressionMode.Compress)
context.Response.AppendHeader("Content-Encoding", "gzip")
Else
context.Response.Filter = New DeflateStream(context.Response.Filter, CompressionMode.Compress)
context.Response.AppendHeader("Content-Encoding", "deflate")
End If
End If
End Sub
''' <summary>
''' Begin the request and process our filter
''' </summary>
''' <param name="source"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub Application_BeginRequest(ByVal source As Object, ByVal e As EventArgs)
Dim application As HttpApplication = DirectCast(source, HttpApplication)
Dim context As HttpContext = application.Context
Dim filePath As String = context.Request.FilePath
Dim fileExtension As String = VirtualPathUtility.GetExtension(filePath)
If fileExtension.Equals(".aspx") Then
application.Response.Filter = New Worker(application.Response.Filter, source)
End If
End Sub
''' <summary>
''' Unneeded for our purposes
''' </summary>
''' <remarks></remarks>
Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
End Sub
''' <summary>
''' Let's do the actual work
''' </summary>
''' <remarks></remarks>
Private Class Worker
Inherits Stream
''' <summary>
''' Get our application context and file stream
''' </summary>
''' <param name="sink"></param>
''' <param name="source"></param>
''' <remarks></remarks>
Public Sub New(ByVal sink As Stream, ByVal source As Object)
_sink = sink
Dim application As HttpApplication = DirectCast(source, HttpApplication)
context = application.Context
End Sub
#Region "Properites"
''' <summary>
''' These properties are a necessary implementation when we inherit IO.Stream
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides ReadOnly Property CanRead() As Boolean
Get
Return True
End Get
End Property
Public Overrides ReadOnly Property CanSeek() As Boolean
Get
Return True
End Get
End Property
Public Overrides ReadOnly Property CanWrite() As Boolean
Get
Return True
End Get
End Property
Public Overrides Sub Flush()
_sink.Flush()
End Sub
Public Overrides ReadOnly Property Length() As Long
Get
Return 0
End Get
End Property
Private _position As Long
Public Overrides Property Position() As Long
Get
Return _position
End Get
Set(ByVal value As Long)
_position = value
End Set
End Property
Private _sink As Stream
Private context As HttpContext
Private html As String, FileCSS As String, FileJS As String
#End Region
#Region "Methods"
''' <summary>
''' Most of these methods are here because they are necessary for inheriting IO.Stream
''' They will make sure we are handling the stream correctly
''' </summary>
''' <param name="buffer"></param>
''' <param name="offset"></param>
''' <param name="count"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer
Return _sink.Read(buffer, offset, count)
End Function
Public Overrides Function Seek(ByVal offset As Long, ByVal origin As SeekOrigin) As Long
Return _sink.Seek(offset, origin)
End Function
Public Overrides Sub SetLength(ByVal value As Long)
_sink.SetLength(value)
End Sub
Public Overrides Sub Close()
_sink.Close()
End Sub
''' <summary>
''' Overwride the default response write, so we can pass the stream back to the browser
''' </summary>
''' <param name="buffer__1"></param>
''' <param name="offset"></param>
''' <param name="count"></param>
''' <remarks></remarks>
Public Overrides Sub Write(ByVal buffer__1 As Byte(), ByVal offset As Integer, ByVal count As Integer)
Dim data As Byte() = New Byte(count - 1) {}
Buffer.BlockCopy(buffer__1, offset, data, 0, count)
html = System.Text.Encoding.[Default].GetString(buffer__1)
'CSS
CompressStyles(html)
'JS
CompressJavascript(html)
Dim outdata As Byte() = System.Text.Encoding.[Default].GetBytes(html)
_sink.Write(outdata, 0, outdata.GetLength(0))
End Sub
''' <summary>
''' Grab our stylesheets from the file stream string
''' concatenate it, minify it, and pass back the originating string while replacing all the stylesheet references
''' with a single refernce to our new stylesheet file
''' </summary>
''' <param name="StrFile"></param>
''' <remarks></remarks>
Private Sub CompressStyles(ByVal StrFile As String)
Dim FilesM As MatchCollection, FileName As String
FilesM = Regex.Matches(StrFile, "<link.*?href=""(.*?)"".*? />")
Dim M(FilesM.Count - 1) As String
For i As Long = 0 To M.Length - 1
'This is the file
M(i) = FilesM(i).Groups(1).Value
FileName = HttpContext.Current.Server.MapPath(M(i).Replace("/", "\"))
FileName = FileName.Replace("\\\", "\")
If File.Exists(FileName) Then
Using objFile As New StreamReader(FileName)
FileCSS += objFile.ReadToEnd
objFile.Close()
End Using
FileCSS = Regex.Replace(FileCSS, "/\*.+?\*/", "", RegexOptions.Singleline)
FileCSS = FileCSS.Replace(" ", "")
FileCSS = FileCSS.Replace(vbCr, "")
FileCSS = FileCSS.Replace(vbLf, "")
FileCSS = FileCSS.Replace(vbCrLf, "")
FileCSS = FileCSS.Replace(vbTab, " ")
FileCSS = FileCSS.Replace("\t", "")
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(";}", "}")
FileCSS = Regex.Replace(FileCSS, "/\*[^\*]*\*+([^/\*]*\*+)*/", "$1")
End If
Next
FilesM = Nothing
If context.Cache("CSS") Is Nothing Then
context.Cache.Add("CSS", FileCSS, Nothing, DateTime.Now.AddDays(31), Cache.NoSlidingExpiration, CacheItemPriority.High, Nothing)
End If
Dim tmp As MatchCollection
tmp = Regex.Matches(StrFile, "<link.*?href=""(.*?)"".*? />")
Dim tmpCt As Long = 0
For Each tmpS As Match In tmp
tmpCt += 1
If tmpCt = tmp.Count Then
StrFile = StrFile.Replace(tmpS.Groups(0).ToString(), "<link type=""text/css"" rel=""stylesheet"" href=""/Style.aspx"" />")
Else
StrFile = StrFile.Replace(tmpS.Groups(0).ToString(), String.Empty)
End If
Next
tmp = Nothing
html = StrFile
End Sub
''' <summary>
''' Grab our scripts from the file stream string
''' concatenate it, minify it, and pass back the originating string while replacing all the scripts references
''' with a single refernce to our new scripts file
''' </summary>
''' <param name="StrFile"></param>
''' <remarks></remarks>
Private Sub CompressJavascript(ByVal StrFile As String)
Dim FilesM1 As MatchCollection, FileName As String
FilesM1 = Regex.Matches(StrFile, "<script.*?src=""(.*?)"".*?></script>")
Dim M1(FilesM1.Count - 1) As String
For j As Long = 0 To M1.Length - 1
'This is the file
M1(j) = FilesM1(j).Groups(1).Value
FileName = HttpContext.Current.Server.MapPath(M1(j).Replace("/", "\"))
FileName = FileName.Replace("\\\", "\")
If File.Exists(FileName) Then
Using objFile1 As New StreamReader(FileName)
FileJS += objFile1.ReadToEnd
objFile1.Close()
End Using
FileJS = Regex.Replace(FileJS, "(// .*?$)", "", RegexOptions.Multiline)
FileJS = Regex.Replace(FileJS, "(/\*.*?\*/)", "", RegexOptions.Multiline)
FileJS = FileJS.Replace(" ", " ")
FileJS = FileJS.Replace(vbCr, vbLf)
FileJS = FileJS.Replace(vbCrLf, vbLf)
FileJS = FileJS.Replace(vbTab, " ")
End If
Next
If context.Cache("JS") Is Nothing Then
context.Cache.Add("JS", FileJS, Nothing, DateTime.Now.AddDays(31), Cache.NoSlidingExpiration, CacheItemPriority.High, Nothing)
End If
FilesM1 = Nothing
Dim tmp1 As MatchCollection
tmp1 = Regex.Matches(StrFile, "<script.*?src=""(.*?)"".*?></script>")
Dim tmpCt1 As Long = 0
For Each tmpS As Match In tmp1
tmpCt1 += 1
If tmpCt1 = tmp1.Count Then
StrFile = StrFile.Replace(tmpS.Groups(0).ToString(), "<script type=""text/javascript"" src=""/Script.aspx""></script>")
Else
StrFile = StrFile.Replace(tmpS.Groups(0).ToString(), String.Empty)
End If
Next
tmp1 = Nothing
html = StrFile
End Sub
#End Region
End Class
End Class
Once you have added in the module, you will need to make a modification to register the module for the application.
一旦添加了模块,就需要进行修改以为应用程序注册模块。
This section goes in your web.config file for the site:
此部分位于站点的web.config文件中:
<add name="DynamicCompression" type="DynamicCompression"/>
and needs to be in the <system.web><httpModules> section.
并且必须位于<system.web> <httpModules>部分中。
Now that we have the grunts out of the way, let's add in our 'extra' files that will be needed to process these resources:
现在,我们已经不再烦恼了,让我们添加处理这些资源所需的“额外”文件:
First we have Script.aspx which needs to be placed in the root of the website.
首先,我们需要将Script.aspx放置在网站的根目录中。
<%@ Page Language="VB" %>
<%
'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
%>
Next we'll add in Script.aspx and that needs to be placed in the root of the website as well.
接下来,我们将添加Script.aspx,并将其也放置在网站的根目录中。
<%@ Page Language="VB" %>
<%
'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
%>
All that is left is to include your stylesheets and javascript includes and you are good to go.
剩下的就是包含您的样式表和javascript包含,您很高兴。
Here is our Default.aspx that is the default page for the site...real simple:
这是我们的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>
And that would be that. Compile the site and run it, and you will see the benefits of the work at hand.
那就是那样。 编译站点并运行它,您将看到手头工作的好处。
Please make sure to leave me any suggestions, improvements, and/or comments you may have, as I am always open for improvements.
请务必留下任何建议,改进和/或意见,因为我一直乐意改进。
Happy Coding!
编码愉快!
p.s. Here is the complete project: Test.zip
ps这是完整的项目: Test.zip
合并脚本和样式表