Async JavaScript and get rid of the all.js in the Storefront #3310
Replies: 7 comments 10 replies
-
Thanks for this proposal. |
Beta Was this translation helpful? Give feedback.
-
I suggest to add a filter for each JS to allow each to load as needs be. Possible filters might be
|
Beta Was this translation helpful? Give feedback.
-
I think this is a very sensible and logical thing to implement, and it should probably also be done for CSS; it makes no sense to have an Currently we are some devs who are trying to hack around it. E.g. We use Puppeteer at my workplace to create a "critical" css file that we load in addition to the existing CSS. This is slower overall, and I greatly dislike it, but at least it helps load the critical elements faster without too much shifting. We have also had talks about eliminating Shopware's own JavaScript entirely because it is so poorly optimized, but it seems like a very difficult thing to do without creating your own Storefront. Currently we are in the process of creating our own tailwind based theme to avoid Shopware's bloated CSS, but the JavaScript may be more difficult to avoid without recreating the entire JS as well. Because of this suggestion, I think we will wait and see before deciding on what to do about the JS. |
Beta Was this translation helpful? Give feedback.
-
Would it also be possible to get rid of Webpack and use Vite instead or would this delay fixing the core issue too much? |
Beta Was this translation helpful? Give feedback.
-
We are currently at this also, for critical css the only viable solution seems to generate the critical css using penthouse. But this has a major drawback as penthouse uses puppeteer under the hood - and this leads to a lot of trouble with missing libraries or obscure errors on the ci/deployment server. For now we use our own little custom server that accepts an URL and generates the critical css on demand, sending it back to the SW installation. Critical CSS is generated by using current route + salesChannelId as key then stored to cache. All of this is a weird drag but works - I would really love to see some solution which eliminates all of that. Also splitting js is the last corner piece for 100% on pagespeed scores. |
Beta Was this translation helpful? Give feedback.
-
Included in the v.6.6 RC: https://www.shopware.com/en/news/shopware-6-6-rc/#66-rc-storefront-changes 🔥 |
Beta Was this translation helpful? Give feedback.
-
How about mention it in the official documentation.... |
Beta Was this translation helpful? Give feedback.
-
Problem
Currently, every JavaScript that is imported into an app's or plugin's
main.js
entry file and built with ourcomposer build:js
command will end up in the Storefrontall.js
. This is also the case for JavaScript which is added to the core Storefront.The
all.js
file is static on all routes and is included (hard-coded) in the base twig template.This is not ideal, because the shop is shipping a lot of unused JavaScript. Furthermore, with more installed apps the
all.js
gets bigger.Solution
Async JS-plugin system and allow dynamic imports
The majority of the Storefront JavaScript is written in JS-plugins. Currently, the JS-plugin system is completely synchronous.
All JS-plugins are imported synchronously into the
main.js
entry-point before the JS-plugin registration and will therefore always end up in theall.js
.No matter if those registered JS-plugins will ever be used on a specific page of the Storefront.
Current way
New way
Instead of importing all JavaScript plugin files synchronously into the
main.js
, we want to offer an async approach inwindow.PluginManager.register()
.This new async way is optional and allows it to pass an arrow function containing a dynamic import to the plugin file.
Not all JS-plugins have to be async, for example, JS-plugins which are supposed to be on every page.
After the async JS-plugin registration the initialization phase
window.PluginManager.initializePlugins()
checks automatically if a registered JS-plugin is async and fetches it on-demand when the plugin selector (e.g.[data-cross-selling]
) is found in the current DOM. This way the current DOM determines if a JS-plugin will be loaded.Getting rid of the
all.js
We want to ship all JS files from an apps dist folder
<app-root>/Resources/app/storefront/dist/storefront/js/
to<platform-root>/public/theme/<theme-hash>/js/
instead of creating anall.js
on the fly.Currently, shopware only considers a single JS file within the apps
/dist
folder that matches the app's technical name, e.g.<app-root>/Resources/app/storefront/dist/storefront/js/my-listing-app.js
.Those dist files are collected by shopware and merged into the
all.js
during thetheme:compile
process.Every app that has a
main.js
entry point will be in theall.js
later.If we want to offer the possibility to fetch JavaScript files async, we need them physically inside the
<platform-root>/public/theme/<theme-hash>/js/
, so the browser can actually fetch them later.Current way
Let's look at this example with 2 installed apps:
Shopware merges all the apps JS
fancy-box.js
andmy-listing.js
into theall.js
and distributes thisall.js
to the
<platform-root>/public/theme/<theme-hash>/js/
folder. All other JS-bundles which might have been created by Webpack are not considered.In the theme folder, as well as in the template, we only have the
all.js
which looks like this:New way
Instead of merging everything into the
all.js
on the fly duringtheme:compile
, we want to keep the apps bundled JS filesand distribute them to the Sales Channel/Theme folder as they are. Each app/plugin gets a separate directory containing their JS files.
Not all JS-bundle files within the
/public/theme
directory will automatically generate a<script>
-tag in the template.Only the "main" JS-bundle which is named like the apps technical name (similar to the old way) will also generate a
<script>
-tag.All other bundles are inside the
/public/theme
directory, so they can eventually be fetched later by the main bundle:Async JS on-demand
If we distribute all bundled JavaScript from
<app-root>/Resources/app/storefront/dist/storefront/js/
to<platform-root>/public/theme/<theme-hash>/js/
it would also allow dynamic imports on-demand and not only using the async JS-plugin system:
Working example (POC)
We have created a working example of the async PluginManager outside of shopware in StackBlitz.
It contains a few async example JS-plugins. The PluginManager and it's dependencies are copied over from platform and adjusted to support async JS-plugins:
⚡ A working example in StackBlitz
Benefits
all.js
on every page/routetheme:compile
Break strategy
We consider this as a breaking change because of several reasons:
PluginManager.override()
, the override will not take effect (without adjustments) because the JS-plugin is was not yet loaded.dist/technical-name.js
in case they are using async JS.New feature flag
Because of those breaks we want to make these changes opt-in via a new feature flag
STOREFRONT_NEW_JS_BUNDLING
.The feature flag needs a few addtional workarounds because the current static imports inside the
main.js
cannot be made inside anif
condition.If we would keep the static imports, the new dynamic imports would not have an effect because the files are already imported. Therefore, we will need something like an additional
main.js
entry point which is used when the feature flag is active:Default entry-point (
STOREFRONT_NEW_JS_BUNDLING=0
)Async entry-point (
STOREFRONT_NEW_JS_BUNDLING=1
)The same principle would apply to our
webpack.config.js
. A new config containing the needed adjustments will be used when the flag is active:How JS-plugins are written
When writing a JS-plugin which extends from
PluginBaseClass
there are currently no changes required to the JS-plugin class itself. In order to make it async, it is enough to change the import to a dynamic import duringPluginManager.register()
and thePluginManager
will load it async automatically as described above. Otherwise, the plugin will be static/global.How JS-plugins are overwritten
This is currently one of the areas that require a change, since the plugin you are trying to override was not loaded yet.
Therefore the import of the class you are extending also needs to be async at the moment.
However, this is still under development and we are trying to find a more elegant solution for this.
Beta Was this translation helpful? Give feedback.
All reactions