Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/407.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 外部生成的静态内容(ASP.NET和browserify)的指纹识别_Javascript_Css_Asp.net_Node.js_Bundling And Minification - Fatal编程技术网

Javascript 外部生成的静态内容(ASP.NET和browserify)的指纹识别

Javascript 外部生成的静态内容(ASP.NET和browserify)的指纹识别,javascript,css,asp.net,node.js,bundling-and-minification,Javascript,Css,Asp.net,Node.js,Bundling And Minification,Nodejs在构建模块化js应用程序时非常出色。如果gulp也是设置的一部分,那么管理和解决依赖关系的工作流、正确绑定、使用sourcemaps进行uglify、自动填充、jshint、测试。。。这对于css以及预处理、自动前缀、linting、嵌入资源和生成文档都非常方便 TL;DR:通过npm/bower,您可以访问大量的前端库生态系统,使NodeJ非常适合构建(不一定服务!)客户端代码。事实上,在客户端代码中使用它是非常棒的,因此在VS 2015中,npm、bower和grunt/gulp

Nodejs在构建模块化js应用程序时非常出色。如果
gulp
也是设置的一部分,那么管理和解决依赖关系的工作流、正确绑定、使用sourcemaps进行uglify、自动填充、jshint、测试。。。这对于css以及预处理、自动前缀、linting、嵌入资源和生成文档都非常方便

TL;DR:通过npm/bower,您可以访问大量的前端库生态系统,使NodeJ非常适合构建(不一定服务!)客户端代码。事实上,在客户端代码中使用它是非常棒的,因此在VS 2015中,
npm
bower
grunt
/
gulp
将得到开箱即用的支持。同时,我们设置了一个gulp任务,该任务运行预构建并编写distjs/css(捆绑输出)


使用指纹URL引用外部静态内容的好方法是什么?从长远来看,我们最好能够完整地分离客户端内容,这样就可以独立构建并部署到CDN,而无需构建应用程序的其余部分。

每次发布后只需对其进行版本设置,然后更新url:


CSS问题

由于CSS引用了图像的相对url,这些url可能也会发生变化,因此在启动应用程序之前,您需要计算大量的哈希计算,这将减慢签名url的生成。事实证明,编写跟踪上次修改日期的代码不适用于CSS图像URL。所以,如果css中引用的任何图像发生更改,css也必须更改

像jquery-1.11.1.js这样的单个文件版本控制问题

首先,它打破了源代码版本控制,Git或任何版本控制都会将app-script-1.11.js和app-script-1.12.js识别为两个不同的文件,很难维护历史记录

对于jQuery,它将在构建库时起作用,并且通常在页面上包含资源时不会更改它,但是在构建应用程序时,我们将有许多JavaScript文件,并且更改版本将需要更改每个页面,然而,单个包含文件可能会执行,但是考虑大量CSS和大量图像。 缓存为URL前缀的上次更新日期

所以我们必须提出静态内容的版本控制,比如
/cached/lastupdate/
,这只是静态资产的url前缀
lastupdate
是请求文件的最后更新日期和时间。还有一个监视程序,如果文件在应用程序范围内被修改,它将刷新缓存密钥

最简单的方法之一是在URL中使用版本密钥

定义应用程序中的版本设置,如下所示

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

这保留了基于上次修改的版本,但这增加了每次请求时对
System.IO.FileInfo
的调用,但是您可以创建另一个字典来缓存此信息并观察更改,但这是一项大量工作。

什么是“指纹url”?@dandavis:这是一种在使用资产时使过时内容无效的技术。这种行为(缓存破坏)通常是通过引用URL中的文件版本或散列“指纹”来完成的,即
app.js?v=123
;无论何时发布更新,都会从不同的URL提供文件。从客户端可以看到,是否所有URL都硬编码到html中作为属性,如src和href?@dandavis:我不确定是否遵循;指纹url被呈现到页面中,如果这是你要问的。那么,url显示的每个地方都是重建后需要更新的地方。如果使用某种类型的客户端资源加载程序,则可以保留一个未永久烘焙的文件,其中包含资源的所有URL。这样,您就可以更新单个文件,并同时更新所有用户或某些组。目前,当你更新资源时,你基本上必须更新你的链接,如果你想在很多地方指向未过期的URL,没有简单的方法。基于JS的加载程序可能会有所帮助,但对于CSS来说仍然不是很好…每次发布后的版本控制都会使每个资源的缓存失效,即使它没有被修改。详细的实现,谢谢。不过,最好是对单个静态资产进行版本设置,而不是对整个资产集进行版本设置,例如,一个版本更改了a.js,但没有更改b.js或c.js,但这三个版本都会使用新的src呈现其脚本标记。在进行许多更改时,单个版本通常比较困难,尤其是在敏捷方法中,然而,像jquery-1.11.1.min.js这样的公共资源可以直接从google CDN加载,并且这个缓存应该只用于我们自己的资源文件。我同意每个版本的开销很小,但它只是隔离了单个版本中的所有依赖资源。另一种选择是使用文件修改时间,而不是使用版本,这将强制URL嵌入文件修改时间,但这也将强制检查文件修改时间(即系统文件调用),每次向IIS发出请求时。事实证明,对于每个请求,此调用都是不必要的调用,最好保留全局版本。
 <script src="@CachedRoute.CachedUrl("/scripts/jquery-1.11.1.js")"></script>
 <script src="/cached/2015-12-12-10-10-10-1111/scripts/jquery-1.11.1.js"></script>
 <script 
      src="//cdn111.cloudfront.net/cached/2015-12-12-10-10-10-1111/scripts/jquery-1.11.1.js">
 </script>
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;
    }
}
    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);
    }