/// <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");
        }
Exemple #2
0
        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);
                    }
                }
            }
        }
Exemple #3
0
        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);
        }
Exemple #4
0
        /// <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");
        }
Exemple #5
0
        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;
        }
Exemple #6
0
        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);
        }
Exemple #7
0
        /// <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);
        }
Exemple #8
0
        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);
        }
Exemple #9
0
        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);
        }
Exemple #10
0
        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);
        }
Exemple #11
0
        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);
        }
Exemple #12
0
        /// <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());
        }
Exemple #13
0
        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);
            }
        }
Exemple #14
0
        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();
        }
Exemple #15
0
        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;
        }
Exemple #16
0
        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);
        }