JavaScriptファイルをダウンロードします。 Ctrl-F5の問題を解決する

私たちは皆、スクリプトをロードする100の方法を知っています。 それぞれに長所と短所があります。



js-ファイルをロードする別の方法を紹介したいと思います。 また、そのような方法がネットワーク上で積極的に使用されていることも理解していますが、それに関する記事を見たことはありません。

したがって、あなたもそれを好きになることを期待して、私は自分自身を使用する方法を説明します。



目的: 開発モジュール、ダウンロード速度、有効なキャッシュ

ボーナス:ダウンロードインジケーター



UPD。 彼は、この方法の主な目標である有効なキャッシュについて概説しました。

この方法を使用する場合、スクリプトが更新されるかどうか、およびエンドユーザーに対して機能するかどうかについて不確実性はありません。



UPD 2.最後まで読んでいない人(私はあなたを完全に理解している)のために、最後にはすべてがもっと簡単にできる方法が言われています。

core.633675510761.jsの代わりにcore.js?V = 633675510761を記述します。 また、これほど多くのことが書かれている理由も示しています。



UPD 3. david_mzWebByteからのコメントで、リクエストを処理するためにJSHandlerではなくurlrewriteを使用する提案がなされました。







モジュール性により、システムの各コンポーネントは、core.js、utils.js、control.js、button.jsなどの個別のファイルにあることを理解しています。

ページをロードするときでも、この原則を拒否しません。 1つのファイルを10 10Kbよりも100Kb速くダウンロードすることは知っていますが。

この問題をさらにキャッシュすることで解決します。



ダウンロード速度-これらは、ページをできるだけ早く表示するためのあらゆる種類のトリックです。

上記のパッケージでスクリプトを組み合わせる方法。 主な欠点は次のとおりです。



したがって、最小化、圧縮、およびキャッシュは残ります。

私も圧縮を拒否しました ファイルのダウンロード速度の向上は、解凍の速度で失われると考えられています。

キャッシング ここに私の方法ハイライトがあります



If-Modified-SinceおよびIf-None-Match(ETag)ヘッダーを使用することに加えて、1年でExpiresをインストールします!

今、なぜ私は大胆にやっているのか、そして私のファイルが1年間有効であることを確信しています。



なぜなら、私はファイル名にその最終変更の日付を割り当てているからです!

つまり core.jsがあり、このようにインクルードされます





すべて、このファイルの次の変更はその名前をcore.635675530761.jsに変更し、完全に新しいスクリプトがロードされます。

次に、この方法の利点をリストしますが、すぐに明らかではありません。





明確にするために、スクリーンショットをいくつか示します。 インターネットは非常に遅いです。

ページへの最初のアクセス。



第二:



1つのファイルを変更します-3番目の呼び出し





2番目と3番目の図からわかるように、ブラウザーは変更されたスクリプトを更新しました。 また、変更についてすべてのファイルを確認することに煩わされなかったこともわかります。 つまり ページには多くの写真がありますが、何らかの理由で2つしかチェックしていません。 スクリプトでも同じことが起こります。 それらは常に更新されるわけではありません。 Webサーバーは、Set-Expires +(1-9999)分などの静的ファイルの追加ヘッダーを設定できます。 さらに、ブラウザとプロキシサーバーの内部ロジック。 一般的に、影響を与えられないもの。



それは理論でした。 これを実践するのは簡単です。

ASP.NETでこれを解決する方法の例を示します。 ステップバイステップ。



1.ページにファイルを含めるには、含まれているファイルの一意性をチェックする特別なオブジェクトを使用します。 そして、レンダリング時に、日付のプレフィックスを付けてファイルに書き込みます。



public class ScriptHelper

{

protected StringCollection includeScripts = new StringCollection();



public void Include( String filename )

{

filename = filename.ToLower();

StringCollection container;

switch ( System.IO.Path.GetExtension( filename ) )

{

case ".js" : container = includeScripts; break ;

default : throw new ArgumentException( "Not supported include file: " + filename, "filename" );

}

if ( !container.Contains( filename ) ) container.Add( filename );

}



public void RegisterScripts( Page page )

{

StringBuilder clientScript = new StringBuilder ();

foreach ( String filename in includeScripts )

clientScript.AppendFormat( includeJS, prefix + FileSystemWatcherManager.GetModifiedName( "Scripts/" + filename ) );

page.ClientScript.RegisterClientScriptBlock( page.GetType(), "clientscripts" , clientScript.ToString(), false );

}

}





* This source code was highlighted with Source Code Highlighter .








コメント:

prefix-スクリプトフォルダーの相対パスのプレフィックス

FileSystemWatcherManager-物理ファイルを操作するためのマネージャー。 このクラスは、System.IO.File.GetLastWriteTimeUtc()への頻繁な呼び出しを回避し、単純なファイルシステムモニターラッパーです。 完全なコードを教えてください。



using System;

using System.IO;

using System.Collections. Generic ;



public class FileSystemWatcherManager

{

private static String physicalAppPath;

private static SortedList< String , Int64 > lastModifiedFiles = new SortedList< String , Int64 >();



public static void StartDirectoryWatcher( String directory, String filter )

{

#if DEBUG

return ;

#endif

if ( physicalAppPath == null && System.Web. HttpContext .Current.Request != null )

physicalAppPath = System.Web. HttpContext .Current.Request.PhysicalApplicationPath;



foreach ( String pattern in filter.Split( ',' ) )

{

FileSystemWatcher dirWatcher = new FileSystemWatcher( directory, pattern );

dirWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;

dirWatcher.IncludeSubdirectories = true ;

dirWatcher.EnableRaisingEvents = true ;

dirWatcher.Changed += new FileSystemEventHandler( OnFileSystemChanged );

dirWatcher.Created += new FileSystemEventHandler( OnFileSystemChanged );

dirWatcher.Renamed += new RenamedEventHandler( OnFileSystemRenamed );



UpdateLastModifiedFiles( directory, pattern, true );

}

}



private static void OnFileSystemRenamed( object sender, RenamedEventArgs e )

{

UpdateLastModifiedFiles( Path.GetDirectoryName( e.FullPath ), ( (FileSystemWatcher)sender ).Filter, true );

}



private static void OnFileSystemChanged( object sender, FileSystemEventArgs e )

{

UpdateLastModifiedFiles( Path.GetDirectoryName( e.FullPath ), ((FileSystemWatcher)sender).Filter, true );

}



public static void UpdateLastModifiedFiles( String directory, String filter, Boolean logAction )

{

lock ( lastModifiedFiles )

{

if ( logAction ) WL.Logger.Instance.Log( String .Format( "Update modified files {1} at \"{0}\"" , directory, filter ) );



foreach ( String subDir in Directory .GetDirectories( directory ) )

UpdateLastModifiedFiles( subDir, filter, false );

foreach ( String file in Directory .GetFiles( directory, filter ) )

lastModifiedFiles[file.Substring( physicalAppPath.Length ).ToLower().Replace( '\\' , '/' )] = File .GetLastWriteTimeUtc( file ).Ticks / 1000000;

}

}



public static String GetModifiedName( String clientPath )

{

#if DEBUG

return clientPath;

#endif

lock ( lastModifiedFiles )

{

Int64 ticks;

if ( !lastModifiedFiles.TryGetValue( clientPath.ToLower(), out ticks ) ) return clientPath;

return String .Format( "{0}/{1}.{2}{3}" , Path.GetDirectoryName( clientPath ).Replace( '\\' , '/' ), Path.GetFileNameWithoutExtension( clientPath ), ticks, Path.GetExtension( clientPath ) );

}

}

}





* This source code was highlighted with Source Code Highlighter .








global.asaxで呼び出す



void Application_Start( object sender, EventArgs e )

{

FileSystemWatcherManager.StartDirectoryWatcher( HttpContext .Current.Request.PhysicalApplicationPath, "*.js,*.css" );

}




* This source code was highlighted with Source Code Highlighter .








コメントは不要だと思います。私が注意しているのはDEBUGモードの場合のみです。デバッガがそれらに固執できるように、実際のファイル名を使用しています。



次の項目は、jsファイルハンドラーです。

web.configを介して有効化

<httpHandlers>

<add verb="GET" path="*.js" type="WL.JSHandler"/>

</httpHandlers>









変更時刻のプレフィックスを削除し、実際のファイルを提供するにはハンドラーが必要です。 また、If-None-Match、If-Modified-Since、Headers、LastModified、ETag、およびExpiresを設定します。 ファイル、元のファイル、最小化されたファイル、圧縮されたファイル、権限の検証などを選択することもできます。



服を着たバージョンを持ってきます。

public class JSHandler : IHttpHandler

{

public void ProcessRequest( HttpContext context )

{

try

{

String filepath = context.Request.PhysicalPath;

String [] parts = filepath.Split( '.' );

Int64 modifiedTicks = 0;

if ( parts.Length >= 2 )

{

if ( Int64 .TryParse( parts[parts.Length - 2], out modifiedTicks ) )

{

List < String > parts2 = new List < String >( parts );

parts2.RemoveAt( parts2.Count - 2 );

filepath = String .Join( "." , parts2.ToArray() );

}

}



FileInfo fileInfo = new FileInfo( filepath );

if ( !fileInfo.Exists )

{

context.Response.StatusCode = 404;

context.Response.StatusDescription = "Not found" ;

}

else

{

DateTime lastModTime = new DateTime ( fileInfo.LastWriteTime.Year, fileInfo.LastWriteTime.Month, fileInfo.LastWriteTime.Day, fileInfo.LastWriteTime.Hour, fileInfo.LastWriteTime.Minute, fileInfo.LastWriteTime.Second, 0 ).ToUniversalTime();

String ETag = String .Format( "\"{0}\"" , lastModTime.ToFileTime().ToString( "X8" , System.Globalization.CultureInfo.InvariantCulture ) );

if ( ETag == context.Request.Headers[ "If-None-Match" ] )

{

context.Response.StatusCode = 304;

context.Response.StatusDescription = "Not Modified" ;

}

else

if ( context.Request.Headers[ "If-Modified-Since" ] != null )

{

String modifiedSince = context.Request.Headers[ "If-Modified-Since" ];

Int32 sepIndex = modifiedSince.IndexOf( ';' );

if ( sepIndex > 0 ) modifiedSince = modifiedSince.Substring( 0, sepIndex );

DateTime sinceDate;

if ( DateTime .TryParseExact( modifiedSince, "R" , null , System.Globalization.DateTimeStyles.AssumeUniversal, out sinceDate ) &&

lastModTime.CompareTo( sinceDate.ToUniversalTime() ) == 0 )

{

context.Response.StatusCode = 304;

context.Response.StatusDescription = "Not Modified" ;

}

}

if ( context.Response.StatusCode != 304 )

{

String file = fileInfo.FullName;



/* String encoding = context.Request.Headers["Accept-Encoding"];

if( encoding != null && encoding.IndexOf( "gzip", StringComparison.InvariantCultureIgnoreCase ) >= 0 &&

File.Exists( file + ".jsgz" ) )

{

file = file + ".jsgz";

context.Response.AppendHeader( "Content-Encoding", "gzip" );

}

else*/

if ( File .Exists( file + ".jsmin" ) ) file = file + ".jsmin" ;



if ( context.Request.HttpMethod == "GET" )

{

context.Response.TransmitFile( file );

}



context.Response.Cache.SetCacheability( HttpCacheability.Public );

context.Response.Cache.SetLastModified( lastModTime );

context.Response.Cache.SetETag( ETag );

if ( modifiedTicks != 0 )

context.Response.Cache.SetExpires( DateTime .UtcNow.AddYears( 1 ) );

context.Response.AppendHeader( "Content-Type" , "text/javascript" );



context.Response.StatusCode = 200;

context.Response.StatusDescription = "OK" ;

}

}

}

catch ( Exception ex )

{

WL.Logger.Instance.Error( ex );

context.Response.StatusCode = 500;

context.Response.StatusDescription = "Internal Server Error" ;

}

}



public bool IsReusable { get { return true ; } }

}




* This source code was highlighted with Source Code Highlighter .








備考 気づいたら、元のファイル、または.jsmin、または.jsgzのいずれかを指定します。

これらは最小化された圧縮バージョンであり、サーバーの構築時に別のツールによって自動的に構築されます。 それらへの直接アクセスを禁止するには、web.configに追加します





<httpHandlers>

<add verb="*" path=".jsmin" type="System.Web.HttpForbiddenHandler"/>

<add verb="*" path=".jsgz" type="System.Web.HttpForbiddenHandler"/>

</httpHandlers>









終わる代わりに



おそらく、別のハンドラーの実装には多くの困難が伴うでしょう。

ファイルを処理する簡単な方法をアドバイスできます。 書く代わりに

その後、JSHandlerは必要ありません。 しかし、キャッシュがどのように機能するかはわかりません。 実際、ファイル名は変更されませんが、追加のパラメーターのみが表示されます。 つまり ブラウザまたはプロキシサーバーの内部キャッシュが原因で、Ctrl-F5でも同じ問題が発生する場合があります。

しかし、スクリプトへのアクセス権を確認するために、たとえば、管理者のみに提供するAdminフォルダーから、別のJSHandlerも必要です。

さらに、誰かがJSHanderの実装とファイルシステムモニターを見ると便利で興味深いでしょう。



JSHandlerのタスクが変更キーを遮断するだけの場合、urlrewriteモジュールに置き換えることができます。



明らかに、この方法で、たとえば.cssなどの他のタイプのファイルをロードできます。 私はこれを行います、それは最初のスクリーンショットで見ることができます、私は特別にcssファイルにカーソルを合わせました。

写真など、他のタイプに展開できます。 しかし、これは実用的ではありません。 第一に、必要な名前をコードに書き留めることで苦しめられ、第二に、画像が非常にまれにしか変更されないため、ブラウザのキャッシュに引っかかっても怖くありません。 怖い場合は、この画像の名前を手動で変更してください。



そして、ダウンロードインジケーター付きの約束のボーナス。



スクリプトを初めてロードするときに、ロードプロセスを表示することで、プロセスを視覚的に高速化できます。

ファイルインクルードを書き込む場所を覚えていますか? 実際にコードは次のとおりです。



StringBuilder clientScript = new StringBuilder ();

if ( includeScripts.Count > 0 )

{

clientScript.Append( @"<div id=" "preloader" " style=" "display:none" "><div></div>Loading Scripts...</div>" );

}

clientScript.Append( scriptStart );

if ( includeScripts.Count > 0 )

{

clientScript.Append( @"var pl=document.getElementById(" "preloader" ");pl.style.display=" "" ";pl=pl.firstChild;" );

}

clientScript.Append( scriptEnd );

if ( includeScripts.Count > 0 )

{

Single dx = 100f / includeScripts.Count;

Single pos = 0f;

foreach ( String filename in includeScripts )

{

clientScript.AppendFormat( includeJS, prefix + FileSystemWatcherManager.GetModifiedName( "Scripts/" + filename ) );

clientScript.AppendFormat( @"<script type=" "text/javascript" ">pl.style.width=" "{0}%" ";</script>" , ( Int32 )pos );

pos += dx;

}

}




* This source code was highlighted with Source Code Highlighter .








2つのdivが描画されます。 そして、負荷の幅が大きくなるにつれて2番目になります。

CSSでは、次のようになります。

#preloader { width:218px;height:92px;background:transparent url("../images/preload.jpg") no-repeat scroll left top;position:relative;text-align:right;color:#383922;font-weight:bold;margin-left:20px;margin-right:auto; }

#preloader div { width:0px;height:92px;background:transparent url("../images/preload.jpg") no-repeat scroll left bottom;position:absolute;left:0px;top:0px; }








ご清聴ありがとうございました。 批判、コメントを聞き、質問に答える準備ができている




All Articles