/// <summary> /// Calls the remote endpoint which will generate t he Critical CSS for a particular URL. /// </summary> /// <param name="url">The URL to generate the Critical CSS for.</param> /// <param name="width">The width of the viewport that is considered critical.</param> /// <param name="height">The height of the viewport that is considered critical.</param> /// <param name="fontReplace">If set to true the remote API will receive a POST request. The font map JSON string should be supplied in the BODY.</param> /// <param name="fontReplaceStr">This string represents a JSON object "fontmap". Which is an array of find and replace strings.</param> /// <returns></returns> public string GenerateCritical(string url, string width = "1800", string height = "1200", bool fontReplace = false) { var client = new RestClient(SpeedyGenerationSettings.GetCriticalApiEndpoint()); client.Authenticator = new HttpBasicAuthenticator(SpeedyGenerationSettings.GetCriticalApiEndpointUsername(), SpeedyGenerationSettings.GetCriticalApiEndpointPassword()); var request = new RestRequest(Method.POST); request.AddHeader("accept-encoding", "gzip, deflate"); request.AddHeader("Host", "nodeapicritical.azurewebsites.net"); request.AddHeader("Cache-Control", "no-cache"); request.AddHeader("Accept", "*/*"); request.AddHeader("Authorization", "Basic Y3JpdGljYWw6Z2VuZXJhdG9y"); request.AddHeader("Content-Type", "application/json"); request.RequestFormat = DataFormat.Json; if (fontReplace) { string fontReplaceStr = GetRemoteJsonField(SpeedyGenerationSettings.GetCriticalApiRemoteFontMap()); string duplicatesStr = GetRemoteJsonField(SpeedyGenerationSettings.GetCriticalApiRemoteDuplicates()); string fontSwapStr = GetRemoteJsonField(SpeedyGenerationSettings.GetCriticalApiRemoteFontSwitch()); var requestBody = new RemoteCriticalParameters(); if (!string.IsNullOrWhiteSpace(fontReplaceStr)) { requestBody.FontMap = JsonConvert.DeserializeObject <RemoteCriticalParameters>(fontReplaceStr).FontMap; } if (!string.IsNullOrWhiteSpace(duplicatesStr)) { requestBody.RemoveDuplicates = JsonConvert.DeserializeObject <RemoteCriticalParameters>(duplicatesStr).RemoveDuplicates; } if (!string.IsNullOrWhiteSpace(fontSwapStr)) { requestBody.FontFaceSwitch = JsonConvert.DeserializeObject <RemoteCriticalParameters>(fontSwapStr).FontFaceSwitch; } requestBody.Height = height; requestBody.Width = width; requestBody.Url = url; request.AddParameter("application/json", JsonConvert.SerializeObject(requestBody), ParameterType.RequestBody); } client.Timeout = 300000; // or automatically deserialize result // return content type is sniffed but can be explicitly set via RestClient.AddHandler(); try { var response2 = client.Execute <CriticalJson>(request); var content = response2.Content; return(response2.Data.Result); } catch (Exception ex) { Diagnostics.Log.Error("Speedy Remote API caused an error", ex); } return("API ISSUE"); }
public virtual void RunSync() { if (!SpeedyGenerationSettings.ShouldGenerateViaScheduledTask()) { return; } if (!SpeedyGenerationSettings.IsPublicFacingEnvironment()) { return; } var criticalCSSRepository = ServiceLocator.ServiceProvider.GetService <ICriticalCSSRepository>(); using (var context = ContentSearchManager.GetIndex(GlobalSettings.Index.Master).CreateSearchContext()) { foreach (var result in GetSpeedyPagesByTemplate(context.Index)) { if (result.IsSpeedyEnabledForPage() && result.IsCriticalStylesEnabledAndPossible()) { criticalCSSRepository.UpdateCriticalCSS(result, GlobalSettings.Database.Master); } } } }
private static void BuildSpeedy(SpeedyLayoutModel model) { var speedyLinks = GenerateDeferedLinks(new ThemesProvider()); model.AssetLinks = speedyLinks; model.VanillaJavasript = GetVanillaJavascript(); model.SpeedyJsEnabled = SpeedyGenerationSettings.IsCriticalJavascriptEnabledAndPossible(Sitecore.Context.Item); model.SpeedyCssEnabled = SpeedyGenerationSettings.IsCriticalStylesEnabled(Sitecore.Context.Item); LoadCssIntoModel(model, speedyLinks); }
/// <summary> /// Calls the remote endpoint which will generate t he Critical CSS for a particular URL. /// </summary> /// <param name="url">The URL to generate the Critical CSS for.</param> /// <param name="width">The width of the viewport that is considered critical.</param> /// <param name="height">The height of the viewport that is considered critical.</param> /// <param name="fontReplace">If set to true the remote API will receive a POST request. The font map JSON string should be supplied in the BODY.</param> /// <returns></returns> public string GenerateCritical(string url, string width = "1800", string height = "1200", bool fontReplace = false) { var client = new RestClient(SpeedyGenerationSettings.GetCriticalApiEndpoint()); var request = new RestRequest(Method.POST) { RequestFormat = DataFormat.Json }; if (fontReplace) { var fontReplaceStr = GetRemoteJsonField(SpeedyGenerationSettings.GetCriticalApiRemoteFontMap()); var duplicatesStr = GetRemoteJsonField(SpeedyGenerationSettings.GetCriticalApiRemoteDuplicates()); var fontSwapStr = GetRemoteJsonField(SpeedyGenerationSettings.GetCriticalApiRemoteFontSwitch()); var requestBody = new RemoteCriticalParameters(); if (!string.IsNullOrWhiteSpace(fontReplaceStr)) { requestBody.FontMap = JsonConvert.DeserializeObject <RemoteCriticalParameters>(fontReplaceStr).FontMap; } if (!string.IsNullOrWhiteSpace(duplicatesStr)) { requestBody.RemoveDuplicates = JsonConvert.DeserializeObject <RemoteCriticalParameters>(duplicatesStr).RemoveDuplicates; } if (!string.IsNullOrWhiteSpace(fontSwapStr)) { requestBody.FontFaceSwitch = JsonConvert.DeserializeObject <RemoteCriticalParameters>(fontSwapStr).FontFaceSwitch; } requestBody.Height = height; requestBody.Width = width; requestBody.Url = url; request.AddParameter("application/json", JsonConvert.SerializeObject(requestBody), ParameterType.RequestBody); } client.Timeout = 300000; // or automatically deserialize result // return content type is sniffed but can be explicitly set via RestClient.AddHandler(); try { var response2 = client.Execute(request); return(response2.Content); } catch (Exception ex) { Diagnostics.Log.Error("Speedy Remote API caused an error", ex, this); } return("API ISSUE"); }
public void UpdateCriticalCSS(Item item, string database) { if (item == null) { return; } // Only act if within the master database if ((item.Database != null && string.CompareOrdinal(item.Database.Name, database) != 0) || item.Name == "__Standard Values" || !item.IsSpeedyEnabledForPage()) { return; } if (!item.InheritsFrom(SpeedyConstants.TemplateIDs.SpeedyPageTemplateId)) { return; } // If speedy is enabled for this page and should we generate the CSS if (!item.IsEnabled(SpeedyConstants.Fields.SpeedyEnabled)) { return; } var presentUrl = item.GetUrlForContextSite() + $"?{SpeedyConstants.ByPass.ByPassParameter}=true"; var width = item.Fields[SpeedyConstants.Fields.CriticalViewPortWidth].Value; var height = item.Fields[SpeedyConstants.Fields.CriticalViewPortHeight].Value; if (string.IsNullOrWhiteSpace(width)) { width = SpeedyGenerationSettings.GetDefaultCriticalWidth(); } if (string.IsNullOrWhiteSpace(height)) { height = SpeedyGenerationSettings.GetDefaultCriticalHeight(); } var criticalHtml = string.Empty; // If the setting is turned on to so that this is a public facing environment, then critical HTML can be generated via the hosted Node application on a separate URL. if (SpeedyGenerationSettings.IsPublicFacingEnvironment() || SpeedyGenerationSettings.UseLocalCriticalCssGenerator()) { criticalHtml = _criticalGenerationGateway.GenerateCritical(presentUrl, width, height, true); } item.Fields[SpeedyConstants.Fields.CriticalCss].Value = criticalHtml; }
private static string DownloadCssFile(string url, bool acceptJs = false) { string cssFileCacheKey = $"speedy-external-css-{url}"; string cssFileCache = HttpContext.Current.Cache[cssFileCacheKey] as string; if (SpeedyGenerationSettings.IsDebugModeEnabled()) { cssFileCache = null; } if (!string.IsNullOrWhiteSpace(cssFileCache)) { return(cssFileCache); } else { var uri = HttpContext.Current.Request.Url; var host = uri.Scheme + Uri.SchemeDelimiter + uri.Host + ":" + uri.Port; ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true; var client = new RestClient(host + url); var request = new RestRequest(Method.GET) { RequestFormat = DataFormat.Json }; if (acceptJs) { request.AddHeader("Accept", "application/javascript"); request.AddHeader("Content-Type", "application/javascript"); } // or automatically deserialize result // return content type is sniffed but can be explicitly set via RestClient.AddHandler(); try { var response2 = client.Execute(request); if (response2?.Content != null) { CacheObject(cssFileCacheKey, response2.Content, GetDependencies(null)); } return(response2.Content); } catch (Exception ex) { Diagnostics.Log.Error("Download CSS SpeedyAssetLinksGenerator", ex); } } return(string.Empty); }
/// <summary> /// Queries the state of the command. /// </summary> /// <param name="context">The context.</param> /// <returns>The state of the command.</returns> public override CommandState QueryState(CommandContext context) { Assert.ArgumentNotNull(context, "context"); if (!SpeedyGenerationSettings.IsPublicFacingEnvironment() && !SpeedyGenerationSettings.UseLocalCriticalCssGenerator()) { return(CommandState.Hidden); } if (context.Items.Length != 1) { return(CommandState.Hidden); } var item = context.Items[0]; return(item.IsSpeedyEnabledForPage() ? CommandState.Enabled : CommandState.Disabled); }
public static SpeedyAssetLinks GenerateDeferedLinks(IThemesProvider themesProvider) { if (AssetContentRefresher.IsPublishing() || IsAddingRendering()) { return(new SpeedyAssetLinks()); } var assetsGenerator = new SpeedyAssetLinksGenerator(); var links = assetsGenerator.GenerateSpeedyAssetLinks(themesProvider); var linkSpeedy = new SpeedyAssetLinks(links); var AreScriptsDeferred = SpeedyGenerationSettings.IsCriticalJavascriptEnabledAndPossible(Sitecore.Context.Item); if (AreScriptsDeferred) { string deferredSriptsCacheKey = $"speedy-deferred-page-scripts-{Sitecore.Context.Item.ID}"; string preloadSriptsCacheKey = $"speedy-preload-page-scripts-{Sitecore.Context.Item.ID}"; string deferredSriptsCache = HttpContext.Current.Cache[deferredSriptsCacheKey] as string; string preloadSriptsCache = HttpContext.Current.Cache[preloadSriptsCacheKey] as string; if (SpeedyGenerationSettings.IsDebugModeEnabled()) { deferredSriptsCache = null; preloadSriptsCache = null; } if (!string.IsNullOrWhiteSpace(deferredSriptsCache) && !string.IsNullOrWhiteSpace(preloadSriptsCache)) { linkSpeedy.ClientScriptsRendered = deferredSriptsCache; linkSpeedy.ClientScriptsPreload = preloadSriptsCache; } else { assetsGenerator.GenerateSpeedyScripts(linkSpeedy); CacheObject(deferredSriptsCacheKey, linkSpeedy.ClientScriptsRendered, GetDependencies(null)); CacheObject(preloadSriptsCacheKey, linkSpeedy.ClientScriptsPreload, GetDependencies(null)); } } else { var linksa = AssetLinksGenerator.GenerateLinks(new ThemesProvider()); linkSpeedy.Scripts = linksa.Scripts; } return(linkSpeedy); }
private static string DownloadCssFile(string url, bool acceptJs = false) { string cssFileCacheKey = $"speedy-external-css-{url}"; string cssFileCache = HttpContext.Current.Cache[cssFileCacheKey] as string; if (SpeedyGenerationSettings.IsDebugModeEnabled()) { cssFileCache = null; } if (!string.IsNullOrWhiteSpace(cssFileCache)) { return(cssFileCache); } else { var uri = HttpContext.Current.Request.Url; var host = uri.Scheme + Uri.SchemeDelimiter + uri.Host + ":" + uri.Port; // or automatically deserialize result // return content type is sniffed but can be explicitly set via RestClient.AddHandler(); try { WebClient client = new WebClient(); string reply = client.DownloadString(host + url); // The below is commented out, and originally slipped through to master by mistake. // If your testing the module in development and don't have a valid SSL certificate, you might need to comment it in. //#if DEBUG // ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true; //#endif if (!string.IsNullOrWhiteSpace(reply)) { CacheObject(cssFileCacheKey, reply, GetDependencies(null)); } return(reply); } catch (Exception ex) { Diagnostics.Log.Error("Download CSS SpeedyAssetLinksGenerator", ex); } } return(string.Empty); }
public static SpeedyLayoutModel GetSpeedyLayoutModel() { string text = GetPageKey(HttpContext.Current.Request.Url.AbsolutePath) + "speedysettings"; SpeedyLayoutModel speedyModel = HttpContext.Current.Cache[text] as SpeedyLayoutModel; if (SpeedyGenerationSettings.IsDebugModeEnabled()) { speedyModel = null; } if (speedyModel != null) { return(speedyModel); } SpeedyLayoutModel model = new SpeedyLayoutModel(); model.CacheKey = text; model.SpeedyEnabled = SpeedyGenerationSettings.IsSpeedyEnabledForPage(Sitecore.Context.Item); model.SpeedyJsEnabled = false; model.SpeedyCssEnabled = false; bool isOnePassCookieEnabledAndPresent = SpeedyGenerationSettings.IsOnePassCookieEnabled(Sitecore.Context.Item) && !string.IsNullOrWhiteSpace(HttpContext.Current.Request[SpeedyConstants.CookieNames.OnePassCookieName]); model.ByPassNotDetected = string.IsNullOrWhiteSpace(HttpContext.Current.Request[SpeedyConstants.ByPass.ByPassParameter]) && !isOnePassCookieEnabledAndPresent; if (!model.ByPassNotDetected) { model.SpeedyEnabled = false; } model.VanillaJavasriptAllLoads = GetVanillaJavascriptAllLoades(); if (model.SpeedyEnabled && model.ByPassNotDetected) { BuildSpeedy(model); } else { model.AssetLinks = AssetLinksGenerator.GenerateLinks(new ThemesProvider()); } return(model); }
public virtual SpeedyAssetLinks GenerateSpeedyAssetLinks(IThemesProvider themesProvider) { AssetsArgs assetsArgs = new AssetsArgs(); CorePipeline.Run("assetService", assetsArgs); string text = GenerateCacheKey(assetsArgs.GetHashCode()) + "speedylinks-" + Sitecore.Context.Item.ID; SpeedyAssetLinks assetLinks = HttpContext.Current.Cache[text] as SpeedyAssetLinks; if (SpeedyGenerationSettings.IsDebugModeEnabled()) { assetLinks = null; } if (assetLinks == null || _configuration.RequestAssetsOptimizationDisabled) { assetLinks = new SpeedyAssetLinks(); if (!assetsArgs.AssetsList.Any()) { return(assetLinks); } assetsArgs.AssetsList = (from a in assetsArgs.AssetsList orderby a.SortOrder select a).ToList(); foreach (AssetInclude assets in assetsArgs.AssetsList) { if (assets is ThemeInclude) { AddThemeIncludeSpeedy(assets as ThemeInclude, assetLinks, themesProvider); } else if (assets is UrlInclude) { AddUrlIncludeSpeedy(assets as UrlInclude, assetLinks); } else if (assets is PlainInclude) { AddPlainInclude(assets as PlainInclude, assetLinks); } } CacheLinks(text, assetLinks, GetDependencies(this.DatabaseRepository)); } return(assetLinks); }
/// <summary> /// Throwing the entire CSS block inside the Critical head block is not the most streamlined method. Ideally the web team would use the NPM Critical /// package. However This is fallback for the lazy web team that ultimately just won't do that. /// Loop over the CSS assets, download the contents and append to one big Critical block /// </summary> /// <param name="assetLinks"></param> /// <returns></returns> private static string BuildEntireCssBlock(SpeedyAssetLinks assetLinks) { StringBuilder entireCriticalBlock = new StringBuilder(); // Lookup the filters var nameValueListString = SpeedyGenerationSettings.GetGlobalSettingsItemFromContext()[SpeedyConstants.GlobalSettings.Fields.CSSFilter]; //Converts the string to NameValueCollection System.Collections.Specialized.NameValueCollection nameValueList = Sitecore.Web.WebUtil.ParseUrlParameters(nameValueListString); foreach (var style in assetLinks.PlainStyles) { ApplyStyleFile(nameValueList, entireCriticalBlock, style); } entireCriticalBlock = entireCriticalBlock.Replace("font-family:", "font-display:swap;font-family:"); return(entireCriticalBlock.ToString()); }
public void OnItemSaving(object sender, EventArgs args) { try { if (!SpeedyGenerationSettings.ShouldRegenerateOnEachSave()) { return; } Diagnostics.Log.Info("SpeedyPageOnSaveEvent running", this); var item = Event.ExtractParameter(args, 0) as Item; _criticalCSSRepository.UpdateCriticalCSS(item, Database); } catch (Exception ex) { Diagnostics.Log.Error("Speedy OnItemSaving is falling over", ex, this); } }
public override void Execute(CommandContext context) { if (context.Items.Length != 1) { return; } if (!SpeedyGenerationSettings.IsPublicFacingEnvironment() && !SpeedyGenerationSettings.UseLocalCriticalCssGenerator()) { return; } //var criticalCSSRepository = ServiceLocator.ServiceProvider.GetService<ICriticalCSSRepository>(); var currentItem = context.Items[0]; currentItem.Editing.BeginEdit(); _criticalCSSRepository.UpdateCriticalCSS(currentItem, GlobalSettings.Database.Master); currentItem.Editing.EndEdit(); }
private static void LoadCssIntoModel(SpeedyLayoutModel model, SpeedyAssetLinks speedyLinks) { string largeCiticalCssBlockCacheKey = $"speedy-entire-css-critical-page-block-{Sitecore.Context.Item.ID}"; string largeCriticalCssBlockCache = HttpContext.Current.Cache[largeCiticalCssBlockCacheKey] as string; if (SpeedyGenerationSettings.IsDebugModeEnabled()) { largeCriticalCssBlockCache = null; } if (!string.IsNullOrWhiteSpace(largeCriticalCssBlockCache)) { model.CriticalHtml = largeCriticalCssBlockCache; } else { model.CriticalHtml = BuildEntireCssBlock(speedyLinks); CacheObject(largeCiticalCssBlockCacheKey, model.CriticalHtml, GetDependencies(null)); } model.SpecialCaseCriticalCss = Sitecore.Context.Item.Fields[SpeedyConstants.Fields.SpecialCaseCriticalCss].Value; }
public static SpeedyAssetLinks GenerateDeferedLinks(IThemesProvider themesProvider) { if (AssetContentRefresher.IsPublishing() || IsAddingRendering()) { return(new SpeedyAssetLinks()); } var assetsGenerator = new SpeedyAssetLinksGenerator(); var links = assetsGenerator.GenerateSpeedyAssetLinks(themesProvider); var linkSpeedy = new SpeedyAssetLinks(links); var AreScriptsDeferred = SpeedyGenerationSettings.IsCriticalJavascriptEnabledAndPossible(Sitecore.Context.Item); if (AreScriptsDeferred) { assetsGenerator.GenerateSpeedyScripts(linkSpeedy); } else { var linksa = AssetLinksGenerator.GenerateLinks(new ThemesProvider()); linkSpeedy.Scripts = linksa.Scripts; } return(linkSpeedy); }