Huellas digitales contenido estático generado externamente (ASP.NET + browserify)

Nodejs browserify es excelente cuando se construyen aplicaciones js modulares. Si gulp forma parte de la configuration, el flujo de trabajo se mejora aún más para administrar y resolver dependencies, agrupar, uglificar con sourcemaps, auto-polyfill, jshint, test … Esto es muy útil para css y con procesamiento previo , prefijación automática, borrado, incorporación de resources y generación de documentation.

TL; DR: con npm / bower tiene acceso a un extenso ecosistema de bibliotecas front-end, haciendo que nodejs sea perfecto para build (¡no necesariamente servir!) Código del lado del cliente. De hecho, usarlo para el código del lado del cliente es tan npm que npm , npm y grunt / gulp se npm gulp en VS 2015. Mientras tanto, hemos configurado una tarea npm que npm y escritura dist js / css (salida agrupada).

¿Cuál es una buena manera de hacer reference al contenido estático externo con urls con huellas dactilares? A largo ploop, idealmente podríamos separar el contenido del lado del cliente de manera que pueda ser construido e implementado independientemente en CDN sin tener que build el rest de la aplicación también.

Problemas con CSS

Dado que CSS hace reference a las URL relativas de las imágenes que también pueden cambiar, y deberá calcular gran cantidad de cálculos de hash antes de iniciar su aplicación, lo que ralentizará la generación de url de la firma. Resulta que escribir código para rastrear la última date de modificación no funciona con las URL de image CSS. Entonces, si alguna de las imágenes referidas dentro de css cambia, css también debe cambiarse.

Problemas con el control de versiones de files individuales como jquery-1.11.1.js

Primero rompe el control de versiones del código fuente, Git o cualquier control de versión identificará app-script-1.11.js y app-script-1.12.js como dos files diferentes, será difícil mantener el historial.

Para jquery, funcionará ya que están construyendo una biblioteca y la mayoría de las veces no lo cambiarás mientras incluyes resources en tu página, pero mientras construyes la aplicación, tendremos muchos files JavaScript y cambiar la versión requerirá cambiar cada página, sin embargo, solo incluye file puede hacerlo, pero considere muchos CSS y muchas imágenes.

Fecha de la última actualización en caching como prefijo de URL

Así que tuvimos que devise versiones de contenido estático como /cached/lastupdate/ , esto no es más que un prefijo url para el elemento estático. lastupdate no es más que la última date de actualización del file solicitado. También hay un observador que actualiza la key de caching si el file se modifica durante el scope de la aplicación.

Bueno, uno de los enfoques más fáciles es utilizar una key de versión en la URL.

Defina la versión en la configuration de la aplicación de la siguiente manera

  <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); 

Ahora en cada página, haga reference a su contenido estático como,

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

Mientras renderiza, su página se representará como (sin CDN)

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

Con CDN como

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

Al colocar la versión en la ruta de la URL en lugar de la cadena de consulta, la CDN tiene un mejor performance ya que las cadenas de consulta pueden ignorarse en la configuration de CDN (que generalmente es el caso pnetworkingeterminado).

Clase CachedRoute de 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.GetRequinetworkingString("name"); requestContext.HttpContext.Items["FilePath"] = requestContext.RouteData.GetRequinetworkingString("name"); return (IHttpHandler)this; } } 

Usar time de modificación de file en lugar de versión

  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); } 

Esto conserva la versión basada en la última modificación, pero esto aumenta la llamada a System.IO.FileInfo en cada request, sin embargo, puede crear otro dictionary para almacenar en caching esta información y observar los cambios, pero es mucho trabajo.

Simplemente vuélvalo a versionar después de cada lanzamiento y actualice la url:

https://cdn.contoso.com/libs/module/ 2.2 /module.js