2015-04-19 18 views
9

Nodejs browserify è eccezionale quando si creano applicazioni js modulari. Se gulp è anche parte del setup, il flusso di lavoro is further enhanced per gestire e risolvere le dipendenze, correttamente bundle, uglify con sourcemaps, auto-polyfill, jshint, test ... Questo è molto utile anche per css con pre-elaborazione , prefisso automatico, linting, incorporamento di risorse e generazione di documentazione.Impronte digitali generate da contenuto statico generato esternamente (ASP.NET + browserify)

TL; DR: con npm/bower si accede a un vasto ecosistema di librerie front-end, rendendo Nodej perfetto per creare (non necessariamente servire!) Codice lato client. In effetti, utilizzarlo per il codice lato client è così straordinario che npm, bower e grunt/gulp verranno supportati immediatamente in VS 2015. Nel frattempo, abbiamo impostato un'attività di gulp che esegue la pre-compilazione e la scrittura dist js/css (uscita di raggruppamento).

Qual è un buon modo per fare riferimento a contenuto statico esterno con URL di impronte digitali? Nel lungo periodo saremmo idealmente in grado di separare il contenuto lato client in modo completo in modo che potesse essere costruito e distribuito in modo indipendente su CDN senza dover costruire anche il resto dell'applicazione.

+2

cos'è un "URL di impronte digitali"? – dandavis

+0

@dandavis: è una tecnica che invalida i contenuti non aggiornati quando si utilizzano [caching aggressivo] (https://developers.google.com/speed/docs/insights/LeverageBrowserCaching). Questo comportamento (busting della cache) viene in genere eseguito facendo riferimento alla versione del file o all'hash "fingerprint" nell'URL i.e. app.js? V = 123'; ogni volta che viene rilasciato un aggiornamento, il file viene fornito da un URL diverso. –

+0

come visto dal client, tutti gli URL sono codificati nell'html come attributi come src e href? – dandavis

risposta

1

Problemi con i CSS

Dal riferimenti CSS URL relativi per le immagini che potrebbero cambiare pure, e sarà necessario per calcolare il calcolo dell'hash molto prima di avviare l'app che rallenterà la generazione dell'URL della firma. Risulta che scrivere codice per tenere traccia della data dell'ultima modifica non funziona con gli URL delle immagini CSS. Quindi, se una qualsiasi delle immagini riferite all'interno di css cambia, anche il css deve essere cambiato.

problemi relativi alle singole versioni dei file come jquery-1.11.1.js

In primo luogo si rompe il codice sorgente delle versioni, Git o qualsiasi controllo di versione identificherà app-script-1.11.js e app-script 1.12 .js come due file diversi, sarà difficile mantenere la cronologia.

Per jquery, funzionerà mentre stanno creando una libreria e molto spesso non lo cambierai mentre includi le risorse sulla tua pagina, ma mentre crei un'applicazione, avremo molti file JavaScript e la versione che cambia richiederà la modifica di ogni pagina, tuttavia, il singolo file include potrebbe farlo, ma considera un sacco di css e molte immagini.

cache Ultimo aggiornamento come Prefisso URL

così abbiamo dovuto venire con il controllo delle versioni del contenuto statico come /cached/lastupdate/, questo non è altro che solo un prefisso URL per la risorsa statica. lastupdate non è altro che l'ultima volta data-ora aggiornata del file richiesto. C'è anche un watcher che aggiorna la chiave di cache se il file viene modificato durante l'ambito dell'applicazione.

Uno degli approcci più semplici è l'utilizzo di una chiave di versione nell'URL.

Definire versione impostazioni di app come seguire

<appSettings> 
    <add key="CDNHost" value="cdn1111.cloudfront.net"/> 
</appSettings> 

// Route configuration 

// set CDN if you have 
string cdnHost = WebConfigrationManager.AppSettings["CDNHost"]; 
if(!string.IsEmpty(cdnHost)){ 
    CachedRoute.CDNHost = cdnHost; 
} 

// get assembly build information 
string version = typeof(RouteConfig).Assembly.GetName().Version.ToString(); 

CachedRoute.CORSOrigins = "*"; 
CachedRoute.Register(routes, TimeSpam.FromDays(30), version); 

Ora su ogni pagina, fare riferimento sul suo sito web statico,

<script src="@CachedRoute.CachedUrl("/scripts/jquery-1.11.1.js")"></script> 

mentre il rendering, la pagina sarà reso come (senza CDN)

<script src="/cached/2015-12-12-10-10-10-1111/scripts/jquery-1.11.1.js"></script> 

Con CDN come

<script 
     src="//cdn111.cloudfront.net/cached/2015-12-12-10-10-10-1111/scripts/jquery-1.11.1.js"> 
</script> 

Inserire la versione nel percorso URL anziché nella stringa di query rende il rendimento di CDN migliore in quanto le stringhe di query possono essere ignorate nella configurazione CDN (che di solito è il caso predefinito).

CachedRoute classe da https://github.com/neurospeech/atoms-mvc.net/blob/master/src/Mvc/CachedRoute.cs

public class CachedRoute : HttpTaskAsyncHandler, IRouteHandler 
{ 

    private CachedRoute() 
    { 
     // only one per app.. 

    } 

    private string Prefix { get; set; } 

    public static string Version { get; private set; } 

    private TimeSpan MaxAge { get; set; } 

    public static string CORSOrigins { get; set; } 
    //private static CachedRoute Instance; 

    public static void Register(
     RouteCollection routes, 
     TimeSpan? maxAge = null, 
     string version = null) 
    { 
     CachedRoute sc = new CachedRoute(); 
     sc.MaxAge = maxAge == null ? TimeSpan.FromDays(30) : maxAge.Value; 

     if (string.IsNullOrWhiteSpace(version)) 
     { 
      version = WebConfigurationManager.AppSettings["Static-Content-Version"]; 
      if (string.IsNullOrWhiteSpace(version)) 
      { 
       version = Assembly.GetCallingAssembly().GetName().Version.ToString(); 
      } 
     } 

     Version = version; 

     var route = new Route("cached/{version}/{*name}", sc); 
     route.Defaults = new RouteValueDictionary(); 
     route.Defaults["version"] = "1"; 
     routes.Add(route); 
    } 

    public override bool IsReusable 
    { 
     get 
     { 
      return true; 
     } 
    } 

    public static string CDNHost { get; set; } 

    public override bool IsReusable 
    { 
     get 
     { 
      return true; 
     } 
    } 

    public class CachedFileInfo 
    { 

     public string Version { get; set; } 

     public string FilePath { get; set; } 

     public CachedFileInfo(string path) 
     { 
      path = HttpContext.Current.Server.MapPath(path); 

      FilePath = path; 

      //Watch(); 

      Update(null, null); 
     } 

     private void Watch() 
     { 
      System.IO.FileSystemWatcher fs = new FileSystemWatcher(FilePath); 
      fs.Changed += Update; 
      fs.Deleted += Update; 
      fs.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.FileName; 
     } 

     private void Update(object sender, FileSystemEventArgs e) 
     { 
      FileInfo f = new FileInfo(FilePath); 
      if (f.Exists) 
      { 
       Version = f.LastWriteTimeUtc.ToString("yyyy-MM-dd-hh-mm-ss-FFFF"); 
      } 
      else 
      { 
       Version = "null"; 
      } 
     } 


    } 

    private static ConcurrentDictionary<string, CachedFileInfo> CacheItems = new ConcurrentDictionary<string, CachedFileInfo>(); 

    public static HtmlString CachedUrl(string p) 
    { 
     //if (!Enabled) 
     // return new HtmlString(p); 
     if (!p.StartsWith("/")) 
      throw new InvalidOperationException("Please provide full path starting with /"); 

     string v = Version; 

     var cv = CacheItems.GetOrAdd(p, k => new CachedFileInfo(k)); 
     v = cv.Version; 

     if (CDNHost != null) 
     { 
      return new HtmlString("//" + CDNHost + "/cached/" + v + p); 
     } 
     return new HtmlString("/cached/" + v + p); 
    } 

    public override async Task ProcessRequestAsync(HttpContext context) 
    { 
     var Response = context.Response; 
     Response.Cache.SetCacheability(HttpCacheability.Public); 
     Response.Cache.SetMaxAge(MaxAge); 
     Response.Cache.SetExpires(DateTime.Now.Add(MaxAge)); 

     if (CORSOrigins != null) 
     { 
      Response.Headers.Add("Access-Control-Allow-Origin", CORSOrigins); 
     } 


     string FilePath = context.Items["FilePath"] as string; 

     var file = new FileInfo(context.Server.MapPath("/" + FilePath)); 
     if (!file.Exists) 
     { 
      throw new FileNotFoundException(file.FullName); 
     } 

     Response.ContentType = MimeMapping.GetMimeMapping(file.FullName); 

     using (var fs = file.OpenRead()) 
     { 
      await fs.CopyToAsync(Response.OutputStream); 
     } 
    } 

    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) 
    { 
     //FilePath = requestContext.RouteData.GetRequiredString("name"); 
     requestContext.HttpContext.Items["FilePath"] = requestContext.RouteData.GetRequiredString("name"); 
     return (IHttpHandler)this; 
    } 
} 

Utilizzando file di modifica Ora invece della versione

public static HtmlString CachedUrl(string p) 
    { 
     if (!p.StartsWith("/")) 
      throw new InvalidOperationException("Please provide full path starting with /"); 
     var ft = (new System.IO.FileInfo(Server.MapPath(p)).LastModified; 
     return new HtmlString(cdnPrefix + "/cached/" + ft.Ticks + p); 
    } 

Questa versione mantiene sulla base di ultima modifica, ma questo aumenta chiamata a System.IO.FileInfo a ogni richiesta, tuttavia è possibile creare un altro dizionario per memorizzare queste informazioni e osservare i cambiamenti, ma è molto lavoro.

+0

Attuazione dettagliata, grazie. È preferibile la versione delle singole risorse statiche piuttosto che l'intero set, ad es. una versione cambia a.js ma non b.js o c.js e tutti e tre ottengono i loro tag di script resi con un nuovo src. –

+0

Le singole versioni sono spesso difficili mentre si apportano molte modifiche, specialmente nel metodo agile, tuttavia risorse comuni come jquery-1.11.1.min.js possono essere caricate direttamente da google CDN e questo CachedRoute deve essere utilizzato solo per i propri file di risorse. Sono d'accordo che è un piccolo overhead con ogni versione, ma semplicemente isola tutte le risorse dipendenti nella singola release. –

+0

Altra alternativa è usare File Modified Time e usarlo al posto della versione, questo costringerà l'URL a incorporare il tempo di modifica del file, ma questo forzerà anche i controlli su Tempo di File Modified, (che è la chiamata del file di sistema), ogni volta che una richiesta è fatto per IIS. Si scopre che questa chiamata è chiamata non necessaria per ogni richiesta ed è meglio lasciare la versione globale. –