All.js Uncaught TypeError: Cannot read properties of undefined (reading 'call') nach Update auf 6.4.11.1

Hallo liebe Leute!

Ich habe nun auch ziemlich mit der Problematik gekämpft und habe nach einigem Lesen und Code auseinanderpflücken eine Lösung gefunden.

Die Erklärung warum dieser Fehler passiert ist eigentlich relativ simpel:

Das Shopware 6 Storefront wird mit Webpack compiliert und in verschiedene „Chunks“ verpackt um das Caching Clientseitig zu verbessern. Wenn aber nun eine Ressource vom Shopware System in einem Plugin verwendet wird, die nicht explizit in dem Vendor-Shared-Chunk vorhanden ist, so werden die Chunk-Hashes durch Webpack angepasst. Der Shared Chunk wird mit folgenden Pfaden ausgestattet:

src/plugin-system
src/helper
src/utility
src/service

Man sieht: Wenn man ein Plugin extended [Override Existing Javascript | Shopware Documentation]( wie in der Doku beschrieben) mit einem einfachen import, dann werden in den meisten Fällen auch die Chunk-Hashes neu generiert.

Die Chunk-Hashes sollten aber in der Shopware Basis nicht verändert werden, da sonst Externe Plugins die Funktionalitäten aus dem Core nicht mehr „finden“ können, zwecks fehlender Referenz.

Der bessere weg in diesem Falle ist, das Plugin über den Pluginmanager zu holen und anschließend dieses zu extenden.

const PluginManager = window.PluginManager
const CookiePermissionPlugin = PluginManager.getPlugin('CookiePermissionPlugin ').get('class');

export default class MyCookiePermission extends CookiePermissionPlugin {
}

Soweit - so gut. Ich bin also hergegangen und habe jegliche Referenzen zu Dateien die nicht durch den Vendor-Shared Chunk abgedeckt wurden entfernt. Dies hat dieses eine Chunk-Problem gelöst.

Das nächst Problem lies aber nicht lange auf sich warten:
Mein Plugin verwendet NPM-Packages, die über das eigentliche Chunk-Size-Limit hinausgehen und somit als eigener Chunk in /vendor/shopware/storefront/Resources/app/storefront/dist/js/ abgelegt werden.
Das heißt, dass die Distribution des Plugin daran scheitern würde, da die Dependencies nicht im Plugin selbst mit enthalten wären.
Die Lösung: Das ChunkSplitting von Shopware modifizieren!
Hier habe ich einen eigene cacheGroup für alle Plugins erstellt, die in dieser Environment vorhanden sind.

const pluginsConfigPath = path.resolve(projectRootPath, 'var/plugins.json');
const plugins = require(pluginsConfigPath);

webpackConfig.optimization.splitChunks.cacheGroups.custom = {
    test: (content) => {
      // Ist die Source-File im "custom"-Ordner?
      return content.resource.includes('custom');
    },
    name: content => {
      // modifiziere den Chunk-Name auf den des Plugin um den Chunk 
      // wieder mit dem eigentlichen Plugin zusammenzuführen
      const parsed = path.parse(content.resource);
      let name = 'custom';
      Object.values(plugins).map(plugin => {
        const basePath = plugin.basePath.replace('/src/', '');
        if (basePath.includes('custom') && content.resource.includes(basePath)) {
            name = plugin.technicalName;
        }
      });
      return name;
    },
    reuseExistingChunk: true,
    chunks(chunk) {
      // Check-Funktion die prüft wohin der Chunk einsortiert werden soll
      return !Object.values(plugins).map(item => item.techicalName).includes(chunk.name);
    },
  };

Somit werden auch größere Dependencies mit im Plugin-Chunk geladen. Nun ist noch das Problem mit den TerserPlugin License-Files gewesen. Denn diese sollten auch mit dem Plugin ausgeliefert werden. Hierfür musste ich natürlich auch das TerserPlugin in der Config überschreiben.

// Überschreibe den Minimizer
 webpackConfig.optimization.minimizer = [
    new TerserPlugin({
      terserOptions: {
        ecma: 5,
        warnings: false,
      },
      parallel: true,
      extractComments: {
// Lege fest, wohin die License File kommen soll
        filename: (fileData) => {
          const plugin = Object.values(plugins).find(plugin => fileData.filename.includes(`${plugin.technicalName}`));
          return plugin ? '../../../../../../../' + path.relative(__dirname, path.resolve(plugin.basePath, 'Resources', 'public', 'static', `${fileData.filename}.LICENSE.txt${fileData.query}`)) : `${fileData.filename}.LICENSE.txt${fileData.query}`;
        },
        banner(file) {
          /*
           * Könnte ein Regulärer Ausdruck sein.... könnte...
          *  Schreibe einen Banner in die eigentliche Kompilierte Datei, wenn es ein Plugin ist.
           */
          const pluginName = file.replace('../../../../../../../custom/plugins/', '').replace(/^[a-z]*/i, '').replace('src/Resources/public/static/js/', '').replace('.js.LICENSE.txt', '').replace(/^\//, '');
          if (!pluginName.startsWith('.'))
            return `License information can be found in /bundles/${pluginName}/static/js/${pluginName}.js.LICENSE.txt`;
        },
      },
    }),
  ]

Ich hoffe ich konnte damit so manchen Leuten helfen.

(Ja, ich hätte auch einen Pull-Request machen können, aber das überlass ich gern anderen. Und die Erklärung finde ich hierbei wichtiger, als die Contribution selbst)

Viele Grüße

Martin

1 „Gefällt mir“