Sdílení a sdružování více balíčků dodavatelů do jednoho balíčku pomocí modulů federace modulů Webpack a zásuvných modulů pro rozdělení částí

Úvod

Úložiště Github pro projekt: https://github.com/IvanGadjo/OneVendorsBundle_ModFedPlugin_SplitChunksPlugin

Federace modulů Webpacku je technika, která nám poskytuje pohled na to, jak může vypadat budoucnost mikrofrontendové architektury. ModuleFederationPlugin se schopností sdílet a dynamicky spouštět kód mezi aplikacemi se může pochlubit výkonnými funkcemi, které mají perspektivní budoucnost (více si o něm můžete přečíst zde).

Nápad na tento blogový příspěvek jsem dostal při práci na projektu na mé stáži. Použil jsem modul ModuleFederationPlugin Webpack ke sdílení modulů knihovny komponent i dodavatelů mezi dvěma webovými aplikacemi. Problém byl v tom, že jsem měl ke sdílení 14 modulů různých dodavatelů, ale potřeboval jsem je mít všechny sdružené do jednoho společného bloku dodavatelů, abych snížil zatížení sítě tím, že mám 14 různých požadavků současně. Proto bylo myšlenkou mít všechny balíčky různých dodavatelů sdružené do jednoho, takže když je potřeba knihovna dodavatele, mít pouze jeden požadavek z hostitelské aplikace na vzdálenou aplikaci.

V tomto příspěvku se pokusím demonstrovat sílu použití modulu Webpack ModuleFederationPlugin ke sdílení modulů mezi dvěma jednoduchými webovými aplikacemi, z nichž jedna funguje jako hostitel (app1) a druhá jako vzdálená (app2). Navíc, aby to bylo jednodušší, obě aplikace budou napsány v prostém JavaScriptu. Myšlenka je, že hostitel načte balíčky funkce, která používá jednu metodu Lodash, a také komponentu tlačítka, která používá knihovnu D3, přímo ze vzdálené aplikace pomocí modulu Webpack ModuleFederationPlugin. Nakonec vám ukážu, jak dosáhnout sloučení těchto dvou balíčků knihoven dodavatelů do jednoho balíčku pomocí SplitChunksPluginu Webpack, aby je bylo možné sdílet mezi vzdálenou a hostitelskou aplikací jako jeden kus a zlepšit výkon.

Struktura projektu

Projekt se skládá z hostitelské aplikace – app1, která načítá sdílenou funkci, sdílenou komponentu a balíček dodavatelů ze vzdálené aplikace – app2. Toto je jen jednoduchá ukázka ukazující práci modulů Webpack ModuleFederationPlugin a SplitChunksPlugin. Konečná struktura projektu by měla vypadat takto:

Nastavení

Po vytvoření dvou složek, jedné pro hostitele a druhé pro vzdálenou aplikaci, cd do adresáře Remote_App

Remote_App
Budeme muset inicializovat projekt npm a nainstalovat webpack, abychom mohli vytvářet balíčky našeho kódu, proto spusťte tyto 2 příkazy z terminálu:

  • npm init
  • npm i webpack webpack-cli --save-devDalším krokem je vytvoření složky src, která bude obsahovat naše sdílené moduly

Remote_App/src
Vytvořte nový soubor s názvem bootstrap.js a další složku – sharedModules. Ve složce sharedModules vytvořte naši první sdílenou funkci – mySharedFunction.js. Ponechte tento soubor zatím prázdný.

Remote_App/src/bootstrap.js
Naplňte tento soubor následujícím řádkem:

import('./sharedModules/mySharedFunction');

Aby federace modulů Webpack fungovala, je nejlepší způsob, jak implementovat sdílení mezi kódy, prostřednictvím dynamických importů, jako je tento, i když je také možné sdílení prostřednictvím horlivé spotřeby modulů a jsou podporovány i statické importy sdílených modulů. Je to proto, že sdílené komponenty/dodavatelé se načítají za běhu a je nejlepší je nechat importovat asynchronně. V této souvislosti se můžete obrátit na tuto část dokumentace Webpack.

Remote_App/webpack.config.js
Nyní cd zpět ze zdrojové složky a vytvořte soubor webpack.config.js, což je konfigurační soubor pro použití Webpacku s naší vzdálenou aplikací:

const path = require('path');

module.exports = {
  entry: './src/bootstrap.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true
  },
  mode: 'development'
};

Vstupním bodem by byl náš soubor bootstrap.js. Tento soubor by fungoval jako vstupní bod pro dynamické importy všech sdílených modulů, které byste mohli mít. Každý svazek bude odeslán do složky dist.

Hostitelská aplikace
Stejně jako předtím musíme inicializovat projekt npm a instalační webpack:

  • npm init
  • npm a webpack webpack-cli --save-dev

Hostitelská aplikace/zdroj
Ze stejných důvodů jako v dálkovém ovladači vytvořte soubor bootstrap.js. Vytvořte také prázdný soubor mainLogic.js. Tento soubor bude později obsahovat dynamické importy sdílených modulů.

Hostitelská_aplikace/src/bootstrap.js

import('./mainLogic');

Host_App/webpack.config.js
Konfigurační soubor pro Webpack v této hostitelské aplikaci můžete zkopírovat a vložit ze vzdálené aplikace. Obsahuje téměř stejnou konfiguraci, kromě názvu prop, bude se jmenovat pouze bundle.js, protože budeme mít pouze jeden balíček související s aplikací.

filename: 'bundle.js'

Hostování aplikací

Abychom dosáhli hostování aplikací, používáme webpack-dev-server (je to nástroj založený na CLI pro spuštění statického serveru pro vaše aktiva). Kromě instalace webpack-dev-server potřebujeme také HtmlWebpackPlugin, abychom mohli renderovat html soubory. Proto musíte provést cd v adresáři hostitele i vzdálené aplikace a spustit následující příkazy:

  • npm a webpack-dev-server --save-dev
  • npm a html-webpack-plugin --save-dev

Dále musíme přidat rozšíření obou konfiguračních souborů webpacku, hostitelské aplikace i vzdáleného:

Host_App/webpack.config.js

devServer: {
    static: path.join(__dirname,'dist'),
    port: 3001
  },

Po zahrnutí této možnosti do našeho konfiguračního souboru webpacku hostitele bude obsah ze složky dist vykreslen na portu 3001. Pojďme nyní vytvořit jednu html stránku:

Hostitelská_aplikace/src/template.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= htmlWebpackPlugin.options.title %> </title>
</head>
<body>
    HOST APP
</body>
</html>

htmlWebpackPlugin.options.title pochází z vlastnosti title HtmlWebpackPlugin, kterou definujeme v dalším kroku.

Host_App/webpack.config.js
Nahoře potřebujeme import pro plugin:

const HtmlWebpackPlugin = require('html-webpack-plugin');

V konfiguračním souboru webpacku, který obsahuje naše nastavení HtmlWebpackPlugin, vytváříme také podporu zásuvných modulů:

plugins: [
    new HtmlWebpackPlugin({
      title: 'Host app',
      template: path.resolve(__dirname, './src/template.html')
    })
  ]

Nyní můžete tento příkaz přidat do skriptů npm, které spustí server. V souboru package.json pod skripty přidejte "start": "webpack serve --open" . Nyní, když spustíte npm start v terminálu by měl být server spuštěn na portu localhost:3001. Na obrazovce se zobrazí pouze bílé pozadí s textem „HOST APP“.

Remote_App
Stejné kroky jsou replikovány ve vzdálené aplikaci. Nejprve nainstalujte požadované balíčky npm, poté vytvořte šablonu.html a přidejte skript npm pro spuštění serveru do souboru package.json

Remote_App/webpack.config.js
Aktualizujte soubor webpack.config.js vzdálené aplikace, aby vypadal takto:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  entry: './src/bootstrap.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true
  },
  mode: 'development',
  devServer: {
    static: path.join(__dirname,'dist'),
    port: 3000
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Remote app',
      template: path.resolve(__dirname, './src/template.html')
    })
  ]
}; 

Používání federace modulů a přidávání knihoven dodavatelů

Do této chvíle jsme nastavili pouze počáteční kód pro obě aplikace a hostili je na různých portech. Nyní potřebujeme skutečně využít modul pro federaci modulů Webpacku a další věc, kterou bychom udělali, je sdílet dva moduly – běžnou funkci JS, která využívá funkci z naší první sdílené knihovny dodavatele – Lodash a tlačítko ve stylu knihovny D3 (D3 je knihovnu JS pro manipulaci s dokumenty na základě dat, ale v našem případě ji pro jednoduchost použijeme pouze pro stylování tlačítka).

Remote_App
Začněme dálkovým ovládáním. Nejprve npm nainstalujte knihovny Lodash a D3

  • npm install lodash d3

Remote_App/src/sharedModules/mySharedFunction.js
Funkce, která bude sdílena, se nazývá myFunction(). K odstranění duplikátů z pole čísel použije metodu sortUniq() z Lodashe:

import _ from 'lodash';

export const myFunction = () => {
    let sampleArray = [1,1,2,2,2,3,4,5,5,6];
    let sortedArray = _.sortedUniq(sampleArray);
    console.log('My resulting array: ' + sortedArray);
}

Remote_App/src/sharedModules/mySharedButton.js

import * as d3 from 'd3';  

// create button & fill with text and id param
let d3Btn = document.createElement('button');
d3Btn.setAttribute('id','btn-d3');
d3Btn.appendChild(document.createTextNode('D3 Button'));

// append to the body
let container = document.getElementsByTagName('body');
container[0].appendChild(d3Btn);

// use d3
// change color of text to orange
d3.select('#btn-d3').style('color','orange');   

Prostě vytvoříme tlačítko a pomocí D3 změníme barvu vnitřního textu.

Remote_App/src/bootstrap.js
Dalším krokem je dynamický import modulů, takže soubor bootstrap bude nyní vypadat takto:

import('./sharedModules/mySharedFunction');
import('./sharedModules/mySharedButton');

Remote_App/webpack.config.js
Aby bylo možné používat modul ModuleFederationPlugin, musíme jej zaregistrovat v konfiguračním souboru. Importovat v horní části souboru:

const { ModuleFederationPlugin } = require('webpack').container;

V sekci plugins v konfiguraci registrujeme plugin:

new ModuleFederationPlugin({
      name: 'remoteApp_oneVendorsBundle',
      library: {
        type: 'var',
        name: 'remoteApp_oneVendorsBundle'
      },
      filename: 'remoteEntry.js',
      exposes: {
        './mySharedFunction':'./src/sharedModules/mySharedFunction.js',
        './mySharedButton':'./src/sharedModules/mySharedButton.js'
      },
      shared: [
        'lodash', 'd3'
      ]
    })

Zaregistrujeme název pro naši aplikaci – hostitelská aplikace jej použije ke spojení s dálkovým ovládáním. Registrujeme také skript s názvem remoteEntry.js. Toto bude „magický“ skript, který umožňuje sdílení modulů mezi našimi dvěma aplikacemi a bude automaticky generován při vytváření naší aplikace. Stručně řečeno, pomocí více zásuvných modulů Webpack pod krytem ModuleFederationPlugin může graf závislostí Webpacku také mapovat závislosti vzdáleně a vyžadovat tyto balíčky JS během běhu.
Potřebujeme také mít sdílenou sekci, kam vložíme knihovny dodavatelů, které chceme sdílet s hostitelskou aplikací.

Host_App/webpack.config.js
Jediná věc, kterou musíme udělat v hostitelské aplikaci, je přidat nějaký kód pro konfiguraci modulu ModuleFederationPlugin pro práci se vzdálenou aplikací. Nejprve potřebujeme plugin:

const { ModuleFederationPlugin } = require('webpack').container;

A v sekci pluginů bychom měli mít následující kód:

new ModuleFederationPlugin({
      name: 'hostApp_oneVendorsBundle',
      library: {
        type: 'var',
        name: 'hostApp_oneVendorsBundle'
      },
      remotes: {
        remoteApp: 'remoteApp_oneVendorsBundle'
      },
      shared: [
        'lodash', 'd3'
      ]
    })

Zde musíme zaregistrovat vzdálenou aplikaci, abychom mohli sdílet moduly. V naší hostitelské aplikaci bychom dálkový ovladač odkazovali názvem „remoteApp“, protože jej takto registrujeme v sekci dálkových ovladačů modulu ModuleFederationPlugin. Potřebujeme také sdílení Lodash a D3. Balíčky dodavatele budou načteny společně s balíkem pro sdílenou funkci a tlačítko.

Hostitelská_aplikace/src/template.html
Potřebujeme přidat pouze <script> tag v <head> šablony.html, aby vše fungovalo:

<script src='http://localhost:3000/remoteEntry.js'></script>

Sdílená funkce myFunction() se načte kliknutím na tlačítko a potřebujeme <div> který bude fungovat jako kontejner pro vykreslení tlačítka, proto potřebujeme tento kód v <body> :

<button id="btn-shared-modules-loader" 
  style="display: block; margin-top: 10px;">Load shared modules</button>
<div id='shared-btn-container' style="margin-top: 10px;"></div>  

Host_App/src/mainLogic.js
Pomocí document.getElementById() získáme tlačítko ze šablony.html a přidáme posluchač události onClick, který dynamicky načte sdílenou funkci a balíček tlačítek:

let loadSharedModulesBtn = document.getElementById('btn-shared-modules-loader');
loadSharedModulesBtn.addEventListener('click', async () => {
    let sharedFunctionModule = await import('remoteApp/mySharedFunction');
    sharedFunctionModule.myFunction();
    let sharedButtonModule = await import('remoteApp/mySharedButton');
    let sharedButton = document.createElement(sharedButtonModule.name);
    let sharedButtonContainer = document.getElementById('shared-btn-container');
    sharedButtonContainer.appendChild(sharedButton);
})

Nyní je dobrý nápad sbalit náš kód. Přidejte následující skript npm do package.json obou aplikací:"build": "webpack --config webpack.config.js" . Po provedení npm run build v obou aplikacích uvidíte výsledné dist složky obsahující všechny balíčky vytvořené Webpackem.
Navíc, pokud nyní spustíte obě aplikace a v hostiteli kliknete na tlačítko Načíst sdílené moduly, zobrazí se tlačítko D3, protokol konzoly ze sdílené funkce zobrazí filtrované pole a oba balíčky dodavatele se načtou ze vzdáleného ovladače. Je důležité nejprve spustit vzdálenou aplikaci, nebo pouze znovu načíst hostitele, pokud jste aplikace spouštěli v jiném pořadí.
Pokud v prohlížeči otevřete kartu Síť vývojářských nástrojů, můžeme vidět, že balíčky Lodash, D3 a sdílené moduly se nenačtou bez kliknutí na tlačítko. Po kliknutí se načtou všechny balíčky a v konzole dostaneme zprávu z myFunction() z dálkového ovladače, ale také vidíme sdílené tlačítko. Pokud umístíte ukazatel myši na název balíčků, uvidíte, že skutečně přicházejí ze vzdáleného místa, z localhost:3000.

Dosažení jednoho balíčku dodavatelů

Počáteční použití SplitChunksPluginu Webpack je k dosažení rozdělení kódu – rozdělení kódu na menší balíčky a řízení zatížení zdrojů. Nicméně v mém případě jsem tento proces obrátil - přišel jsem na lstivý způsob, jak jej použít ke spojení kódu všech dodavatelů do jednoho balíčku. V tomto příkladu máme jen malý počet balíčků dodavatelů, ale to může být docela přínosné a optimalizace výkonu při práci ve větším měřítku s mnoha menšími moduly dodavatelů, za předpokladu, že potřebujeme načíst všechny balíky dodavatelů současně.

Remote_App/webpack.config.js

optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/](lodash|d3|delaunator|internmap|robust-predicates)/,
          name: 'Vendors_Lodash_D3',
          chunks: 'all'
        }
      }
    }
}

V případě, že by vás zajímalo delaunator, internmap … To jsou moduly, které se přidávají při instalaci D3, pokud je nezahrnete do regexu, vyrobí si samostatné moduly dodavatele v adresáři dist, což není to, čeho jsme chtěli dosáhnout . Tomu se lze také vyhnout, pokud je D3 importován selektivněji (nemít import * as d3 from d3 ).
Nyní běží npm run build ve vzdálené aplikaci bude mít za následek společný balíček dodavatele ve složce dist s názvem Vendors_Lodash_D3.bundle.js.
Nakonec, pokud spustíte obě aplikace, dálkový ovladač načte celý balíček Vendors_Lodash_D3 sám a nenačte žádné moduly jiných dodavatelů:

Po kliknutí na tlačítko načíst sdílené moduly v hostitelské aplikaci se načtou oba balíčky pro sdílenou funkci a sdílené tlačítko D3, ale také se načte pouze jeden balíček dodavatele - Vendors_Lodash_D3:

Závěr

V tomto příspěvku jsem demonstroval sílu a potenciál použití Webpack ModuleFederationPlugin ke sdílení kódu mezi dvěma webovými aplikacemi. Navíc pomocí chytré kombinace modulů Webpack ModuleFederationPlugin a SplitChunksPlugin můžeme spojit více modulů dodavatelů do jednoho, a tím snížit zatížení sítě a zlepšit výkon načítání balíků mezi aplikacemi.
Doufám, že tento příspěvek byl pro mnohé z vás z komunity užitečný a využijete tuto implementaci ve svých projektech. Velké díky Zacku Jacksonovi @scriptedalchemy za to, že mě přesvědčil, abych napsal blogový příspěvek na toto téma.