Frontend assets building and loading
Edit on GitHubSpryker assets are split into critical and non-critical CSS chunks. Their main purpose is to provide loading of the critical CSS at the start of the page loading and load the non-critical CSS only after the full page is loaded.
The critical CSS includes all the components from the ShopUi module as basic front-end building blocks for all pages and components from the Home page, Catalog page, and Product Detail page. These pages are considered landing pages and the most important pages from the search engine optimization perspective.
The non-critical CSS includes all other styles and service CSS classes called utils. The non-critical assets are not necessary for every page loading and can be loaded when the whole page has already been loaded.
To support the assets loading behavior as described above, most pages considered as not landing are loading both critical and non-critical assets into the <head>
tag at the start of the page loading. The assets loading approach for the landing pages is described further in this article.
Page-critical-path layout
For the landing pages, there is the page-critical-path
layout defining the assets loading behavior. It is an extension of the page-blank
layout with the overwritten nonCriticalStyles
and styleLazyLoader
blocks. The page-critical-path
contains a style-loader
component before the closing tag of <body>
. The style-loader
component is responsible for loading the non-critical CSS only after the whole page is loaded.
The page-critical-path
layout uses cookies to track whether it is the first session on the site. If it is the first session, the critical CSS and utils are loaded into the <head>
tag at the start of the page loading. Only after the whole page is loaded, the style-loader
component appends the non-critical CSS to the end of the <head>
tag. Otherwise, the approach for the non-landing pages is used.
The main purpose of the page-critical-path layout is to use the CSS Lazy Load only for the landing pages. To enable the CSS Lazy Load for your project, see Frontend CSS Lazy Load integration.
Building CSS and JS chunks
As mentioned above, all the Yves CSS are split into critical, non-critical, and utils chunks using the Webpack MiniCssExtractPlugin
and are loaded separately to all pages. Due to the fact that the utils CSS is a part of the non-critical CSS, they are built separately only for the user’s first session on the site if the pages use page-critical-path
layout.
Building the JS chunks using a code-splitting Webpack feature allows splitting JS code into various bundles. It builds a smaller bundles size and allows you to control the resource load prioritization impact on a page load time.
There are three general approaches to code splitting:
- Entry Points: manual split of code using entry configuration.
- Prevent Duplication: using the
SplitChunksPlugin
to dedupe and split chunks. - Dynamic Imports: splitting code via inline function calls within modules.
The approaches are implemented with the RuntimeChunkPlugin
, SplitChunksPlugin
plugins, and by using the WebpackChunkName
. The RuntimeChunkPlugin
is needed for multiple entry points that are being used on a single HTML page. The SplitChunksPlugin
allows extracting common dependencies into entirely new chunks. WebpackChunkName
uses a component name as a chunk name. As a result, at first, Webpack builds a single runtime JS chunk and creates named chunks for all components on the page using the components’ names as names for chunks.
Splitting builds with ES5 and ES6
You can create a JS build in ES5 and ES6 modes and switch between them only if needed, for example, in case of using the old browsers that do not support ES6.
To achieve this, the front-end builder settings have the buildVariantSettings
statement that includes building mode naming (legacy for ES5 and ESM for ES6) and boolean variable isES6Module
. Also, the previous ts-loader
has been changed to babel-loader
and isES6Module
variable is used to switch to ES6 building mode. For the babel-loader
, a preset with plugins @babel/preset-typescript
, @babel/plugin-transform-runtime
and @babel/plugin-proposal-class-properties
is used.
The command list for building fronted is extended with the new attributes module:esm and module:legacy.
To create a frontend build in different ES5 and ES6 modes, use the list of commands from Frontend-related commands. See the YVES section.
Polyfills
A polyfill is a code that is used to provide modern functionality for older browsers that do not natively support it. The polyfills are imported on the project level into the src/Pyz/Yves/ShopUi/Theme/default/vendor.ts
file. In the Spryker front-end, the following polyfills are used:
core-js/promise
- includes polyfills for ECMAScriptPromises
up to 2019, they provide the ability to use promises for old browsers.core-js/array
- provides an ability to use modernArray
’s methods for the old browsers;core-js/set
- includes allSet
-object related features adapted to old browsers.core-js/map
- includes allMap
-object related features adapted to old browsers.whatwg-fetch
- implements a subset of the standard Fetch specification, enough to makefetch
a viable replacement for the most uses ofXMLHttpRequest
in the traditional web applications.element-remove
- a polyfill forElement.remove()
method compatible with the Internet Explorer 9 and higher.classlist-polyfill
- a polyfill for classList andDOMTokenList
that ensures full compliance for all the standard methods and properties ofElement.prototype.classList
for IE10-IE11 browsers; plus nearly compliant behavior for IE 6-9.string.prototype.startswith
- a polyfill for theString.prototype.startsWith
method in ECMAScript 6 that implements the es-shim API interface for ES3-supported environments.date-input-polyfill
- provides support of<input type="date">
without any dependencies in IE, Firefox, and OS X Safari.intersection-observer
- a polyfill for the nativeIntersectionObserver
API in the unsupported IE;elementClosestPolyfill
- provides support of theElement.closest()
for IE9 or higher.webcomponentsjs/custom-elements-es5-adapter
- provides the ability to compile and serve ES6 classes in custom elements HTMLElement for browsers that support only ES5-style classes.
Cache-busting mechanism
The main idea of the cache-busting mechanism is to provide an ability to reset JS/CSS cache for a newly deployed version so that the new assets are delivered to the front-end applications. It is achieved by adding a hash to every asset path via an environment variable SPRYKER_BUILD_HASH
that allows resetting the browser cache instead of a namespace variable. The path of the resources on Yves is displayed with this hash folder assets/SPRYKER_BUILD_HASH/default/css/yves_default.app.css
. If the project is set up via Docker, the assets folder is present in the Docker container in a production environment. Otherwise, if it is a development environment, the assets folder is in a public assets folder rather than a container.
isNewFrontendBuildSupported and isCssLazyLoadSupported Twig variables
The page-blank layout has the isNewFrontendBuildSupported
and isCssLazyLoadSupported
Twig variables on the core level. These variables are responsible for turning on/off the splitting builds with ES5 and ES6 and CSS Lazy loading. By default, these variables are set to false
and overwritten to true
on a project level to enable the mentioned features.
Assets structure
All the Spryker assets are divided into .css, .js files, and images and are added to the public/Yves/assets
folder after the frontend is built. The assets folder has the following structure:
-
current - assets folder for the current namespace (
current
by default)- default - assets folder for the current theme in the namespace
-
css - folder for styles
yves_default.critical.css
- styles loaded at the start of the page loadingyves_default.non-critical.css
- styles loaded after full page loadingyves_default.util.css
- service stylesyves_default.app.css
- all the styles used for cases when CSS Lazy Load feature is turned off
-
js - folder for scripts
yves_default.runtime.legacy.js
/yves_default.runtime.js
- scripts executed on runtime and compiled in ES5/ES6 modesyves_default.app.legacy.js
/yves_default.app.js
- compiled Spryker frontend application script in ES5/ES6 modesyves_default.vendor.legacy.js
/yves_default.vendor.js
- contains scripts of polyfills compiled in ES5/ES6 modes- named scripts of used web components compiled in both ES5 and ES6 modes
-
images - folder for the images
-
- default - assets folder for the current theme in the namespace
-
static folder - static assets source folder
- images - folder for the static images
Depending on the usage of the CSS Lazy Load feature and ES5 supporting mode, the following variants of the scripts and styles loading in the head
and body
tags exist:
<head>
<head>
...
{% block headStyles %}
{% if isCssLazyLoadSupported %}
{# styles loaded at the start of the page loading #}
<link rel="stylesheet" href="{{ publicPath('css/yves_default.critical.css') }}">
{% block nonCriticalStyles %}
{# styles loaded after full page loading #}
<link rel="stylesheet" href="{{ publicPath('css/yves_default.non-critical.css') }}">
{% endblock %}
{% else %}
{# all the styles used for cases when CSS Lazy Load feature is turn off #}
<link rel="stylesheet" href="{{ publicPath('css/yves_default.app.css') }}">
{% endif %}
{% endblock %}
{% block headScripts %}
{% if isNewFrontendBuildSupported %}
{% block esmHeadScripts %}
{# should not be executed in browsers that support ES6 modules #}
<script nomodule type="application/javascript" src="{{ publicPath('js/yves_default.runtime.legacy.js') }}"></script>
{# causes the code to be treated as a JavaScript module, serves browsers that support ES6 #}
<script type="module" src="{{ publicPath('js/yves_default.runtime.js') }}" crossorigin="anonymous"></script>
{% endblock %}
{% else %}
{% block legacyHeadScripts %}
{# fallback for old frontend build system that support only ES6 #}
<script src="{{ publicPath('js/yves_default.runtime.js') }}"></script>
{% endblock %}
{% endif %}
{% endblock %}
...
</head>
<body>
- Split scripts are compiled in ES5 and ES6 modes (
isNewFrontendBuildSupported
) and loaded depending on whether the ES5 is supported or not. By default, only ES6 mode is supported.
<body>
...
{% block footerScripts %}
{% if isNewFrontendBuildSupported %}
{% block esmFooterScripts %}
{# scripts slitted to ES5 and ES6 modes #}
<script nomodule type="application/javascript" src="{{ publicPath('js/yves_default.vendor.legacy.js') }}"></script>
<script nomodule type="application/javascript" src="{{ publicPath('js/yves_default.app.legacy.js') }}"></script>
<script type="module" src="{{ publicPath('js/yves_default.vendor.js') }}" crossorigin="anonymous"></script>
<script type="module" src="{{ publicPath('js/yves_default.app.js') }}" crossorigin="anonymous"></script>
{% endblock %}
{% else %}
{% block legacyFooterScripts %}
{# fallback for old frontend build system that support only ES6 by default #}
<script src="{{ publicPath('js/yves_default.es6-polyfill.js') }}"></script>
<script src="{{ publicPath('js/yves_default.vendor.js') }}"></script>
<script src="{{ publicPath('js/yves_default.app.js') }}"></script>
{% endblock %}
{% endif %}
{%- block styleLazyLoader %}{% endblock -%}
{% endblock %}
...
</body>
Thank you!
For submitting the form