Micro-frontend-ներ
Մեծ ֆրոնտենդի բաժանում ըստ թիմերի. Module Federation, կոմպոզիցիայի pattern-ներ և իրական ծախսերը։
30 վայրկյանում
Micro-frontend-ները մեկ մեծ վեբ հավելվածը բաժանում են ավելի փոքր ֆրոնտենդ մասերի, որոնք տարբեր թիմեր ինքնուրույն կառուցում, թեստավորում և deploy են անում, հետո միավորում օգտատիրոջ համար մեկ փորձառության մեջ։ Սա միկրոծառայությունների ֆրոնտենդ տարբերակն է։ Ճիշտ պատճառներով արված՝ այն թույլ է տալիս բազմաթիվ թիմերի առաջ շարժվել՝ առանց միմյանց արգելափակելու. չափազանց վաղ արված՝ ավելացնում է մեծ բարդություն, կրկնվող կոդ և ավելի դանդաղ էջեր։
Իմ ամենօրյա stack-ում չէ․ գիտեմ հիմնական գաղափարները և մանրամասները կսովորեմ, երբ նախագիծը դա պահանջի։
Ինչու է սա կարևոր
- Մեծ կազմակերպություններում մեկ ֆրոնտենդը, որին տիրապետում են բազմաթիվ թիմեր, դառնում է նեղ տեղ. մեկ release train, մեկ pipeline, անվերջ merge կոնֆլիկտներ։
- Micro-frontend-ները թույլ են տալիս յուրաքանչյուր թիմի ամբողջությամբ տիրապետել բիզնես ոլորտին՝ բազայից մինչև UI, և deploy անել իր ժամանակացույցով։
- Դրանք հնարավոր են դարձնում աստիճանական միգրացիան, օրինակ՝ հին AngularJS կամ jQuery հավելվածը React տեղափոխել էջ առ էջ՝ ռիսկային big-bang վերագրման փոխարեն։
- Pattern-ը հասկանալը նաև օգնում է ճանաչել, երբ այն պետք չէ, ինչը ավելի հաճախակի և արժեքավոր որոշում է։
Ինչպես է աշխատում
01Գաղափարը՝ բաժանել ըստ թիմի և բիզնես դոմենի
Պատկերացրեք e-commerce կայք, որը կառուցում են վեց թիմ՝ որոնում, ապրանքների էջեր, checkout, account, բովանդակություն և մարքեթինգ։ Մեկ ֆրոնտենդ կոդային բազայում յուրաքանչյուր փոփոխություն անցնում է մեկ build-ով և մեկ deploy-ով, և checkout-ի բագը կարող է արգելափակել որոնման թողարկումը։ Micro-frontend-ների դեպքում յուրաքանչյուր թիմ տիրապետում է ուղղահայաց շերտի՝ իր ռեպոզիտորիան կամ փաթեթը, իր pipeline-ը և իր deploy-ը։ Shell-ը (կոչվում է նաև host կամ container app) տրամադրում է էջի շրջանակը, routing-ը և ընդհանուր ծառայությունները, և յուրաքանչյուր ոլորտի համար բեռնում ճիշտ micro-frontend-ը։ Պատկերացրեք առևտրի կենտրոն. մեկ շենք, ընդհանուր միջանցքներ և անվտանգություն, բայց յուրաքանչյուր խանութ գործում է ինքնուրույն։ Հիմնական սկզբունքն է բաժանել ըստ բիզնես կարողության, ոչ թե տեխնիկական շերտի, ինչպես «header թիմ» և «կոճակների թիմ»։
- Shell / host. կազմում է էջը և տիրապետում routing-ին ու գլոբալ հարցերին։
- Remote / micro-frontend. UI-ի ինքնուրույն deploy արվող մաս։
- Contract. shell-ի և remote-ի միջև համաձայնեցված ինտերֆեյսը (props, իրադարձություններ, route-եր)։
02Micro-frontend-ների կոմպոզիցիայի եղանակներ
Կոմպոզիցիան կարող է տեղի ունենալ տարբեր ժամանակներում և տեղերում։ Build-time ինտեգրացիան յուրաքանչյուր մասը հրապարակում է որպես npm փաթեթ, որը shell-ը import է անում. պարզ և արագ, բայց կորցնում եք ինքնուրույն deploy-ը, որովհետև shell-ը պետք է վերակառուցվի։ Server-side կոմպոզիցիան HTML ֆրագմենտները կարում է սերվերում կամ edge-ում (Edge Side Includes, կոմպոզիցիայի ծառայություն կամ ֆրեյմվորքի հնարավորություններ, ինչպես Next.js Multi-Zones-ը, որտեղ տարբեր հավելվածներ տիրապետում են տարբեր URL ուղիների մեկ դոմենի հետևում)։ Run-time ինտեգրացիան յուրաքանչյուր թիմից JavaScript է բեռնում բրաուզերում՝ Module Federation, import map-եր native ES մոդուլներով կամ Web Components։ Iframe-երը տալիս են ամենաուժեղ մեկուսացումը, բայց վատ ինտեգրացիա layout-ի, routing-ի և accessibility-ի համար։ Ամենապարզ արդյունավետ մոտեցումը հաճախ route-ի մակարդակով բաժանումն է. յուրաքանչյուր թիմ տիրապետում է ամբողջական էջերի, և կիսվում է միայն shell-ի նավիգացիան։
03Module Federation-ի բացատրությունը
Module Federation-ը, որը ներդրվել է webpack 5-ում և այժմ հասանելի է որպես ինքնուրույն runtime (Module Federation 2.0)՝ webpack-ի, Rspack-ի, Rsbuild-ի և Vite-ի plugin-ներով, թույլ է տալիս մի build-ին run time-ում մոդուլներ բեռնել մեկ այլ build-ից։ Remote-ը մոդուլներ (օրինակ՝ ./ProductPage) է տրամադրում փոքր entry ֆայլի միջոցով, որը հաճախ կոչվում է remoteEntry.js կամ նկարագրվում mf-manifest.json-ով։ Host-ը հայտարարում է remote-ները և import է անում դրանց մոդուլները, կարծես դրանք լոկալ լինեին. կոդը բերվում է միայն անհրաժեշտության դեպքում։ Ընդհանուր կախվածությունները, ինչպես React-ը, հայտարարվում են որպես singleton-ներ, որպեսզի էջը բեռնի մեկ օրինակ, իսկ տարբերակների միջակայքերը համաձայնեցվում են run time-ում։ 2.0 տարբերակն ավելացնում է manifest պրոտոկոլ, type hint-եր remote-ների համար, runtime plugin համակարգ և devtools։
// rsbuild.config.ts (remote: product team)
import { defineConfig } from "@rsbuild/core";
import { pluginReact } from "@rsbuild/plugin-react";
import { pluginModuleFederation } from "@module-federation/rsbuild-plugin";
export default defineConfig({
plugins: [
pluginReact(),
pluginModuleFederation({
name: "product",
exposes: { "./ProductPage": "./src/ProductPage.tsx" },
shared: {
react: { singleton: true, requiredVersion: "^19.0.0" },
"react-dom": { singleton: true, requiredVersion: "^19.0.0" },
},
}),
],
server: { port: 3001 },
});
04Remote-ի օգտագործումը shell-ում
Host-ը հայտարարում է, թե որտեղ է ապրում յուրաքանչյուր remote, և ծույլ կերպով բեռնում դրա մոդուլները։ Յուրաքանչյուր remote փաթաթեք error boundary-ով և loading fallback-ով. remote-ը ցանցային կախվածություն է, որը կարող է ձախողվել կամ դանդաղ լինել, և մեկ կոտրված թիմը չպետք է կոտրի ամբողջ էջը։ Production-ում remote-ների URL-ները պետք է գան կոնֆիգուրացիայից կամ manifest ծառայությունից, ոչ թե hard-code արված լինեն, որպեսզի թիմերը կարողանան deploy և rollback անել՝ առանց shell-ը վերակառուցելու։
// host: rsbuild.config.ts (excerpt)
pluginModuleFederation({
name: "shell",
remotes: { product: "product@https://cdn.example.com/product/mf-manifest.json" },
shared: { react: { singleton: true }, "react-dom": { singleton: true } },
});
// host: src/routes/ProductRoute.tsx
import { lazy, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
const ProductPage = lazy(() => import("product/ProductPage"));
export function ProductRoute() {
return (
<ErrorBoundary fallback={<p>Product details are unavailable right now.</p>}>
<Suspense fallback={<p>Loading…</p>}>
<ProductPage />
</Suspense>
</ErrorBoundary>
);
}
05Հաղորդակցություն և ընդհանուր state
Micro-frontend-ները պետք է թույլ կապված լինեն, ուստի հաղորդակցվում են փոքր, բացահայտ պայմանագրերով, ոչ թե ընդհանուր գլոբալ store-ով։ Տարածված տարբերակներ. URL-ը և routing-ը որպես հիմնական state (ապրանքի ID-ն ապրում է URL-ում), props, որոնք shell-ը փոխանցում է remote-ը mount անելիս, բրաուզերի custom event-ներ ծանուցումների համար, ինչպես «զամբյուղը թարմացվել է», և բեքենդ API-ները որպես ճշմարտության աղբյուր, որը յուրաքանչյուր մաս կարդում է ինքնուրույն։ Խուսափեք բոլոր թիմերի կողմից կիսվող մեկ գլոբալ Redux store-ից. այն վերստեղծում է հենց այն կապվածությունը, որից խուսափելու համար բաժանել եք։ Authentication-ին սովորաբար տիրապետում է shell-ը, որը token getter կամ user context է տրամադրում փոքրիկ ընդհանուր ինտերֆեյսի միջոցով։
// Contract: a typed custom event
type CartUpdated = { count: number };
// Checkout micro-frontend
window.dispatchEvent(new CustomEvent<CartUpdated>("cart:updated", { detail: { count: 3 } }));
// Shell header
window.addEventListener("cart:updated", (e) => {
const { count } = (e as CustomEvent<CartUpdated>).detail;
renderBadge(count);
});
06Դիզայն համակարգեր և հետևողականություն
Օգտատիրոջ համար տեսանելի ամենամեծ ռիսկը անհետևողական պրոդուկտն է. տարբեր կոճակներ, տարածություններ և վարքագծեր յուրաքանչյուր թիմից։ Ընդհանուր դիզայն համակարգը լուծում է սա. design token-ներ (գույներ, տարածություններ, տիպոգրաֆիա՝ որպես CSS փոփոխականներ), կոմպոնենտների գրադարան՝ հրապարակված որպես տարբերակավորված փաթեթ, և փաստաթղթավորում Storybook-ում։ Թիմերն այն օգտագործում են որպես ցանկացած կախվածություն։ Ոճերի մեկուսացումը նույնպես կարևոր է. օգտագործեք CSS Modules, նախածանց յուրաքանչյուր micro-frontend-ի համար կամ Shadow DOM Web Components-ի համար, որպեսզի մի թիմի գլոբալ CSS-ը չկոտրի մյուսի էջը։ Դիզայն համակարգի կոտրող փոփոխությունները պահեք հազվադեպ և հայտարարված, որովհետև յուրաքանչյուր թիմ պետք է թարմացնի. token-ները որպես CSS փոփոխականներ թույլ են տալիս թեման փոխել run time-ում՝ առանց յուրաքանչյուր remote-ը նորից deploy անելու։
07Monorepo, թե՞ polyrepo, և բուն որոշումը
Micro-frontend-ները ինքնուրույն deploy-ի մասին են, ոչ պարտադիր առանձին ռեպոզիտորիաների։ Շատ թիմեր օգտագործում են monorepo (Nx, Turborepo, pnpm workspaces)՝ մեկ թղթապանակ յուրաքանչյուր micro-frontend-ի համար, առանձին build և deploy pipeline-եր, որոնք գործարկվում են ազդված փոփոխություններով, և ընդհանուր գործիքներ։ Սա refactor-ները և կախվածությունների թարմացումները պահում է հեշտ՝ պահպանելով թիմերի սեփականությունը։ Polyrepo-ները առավելագույնի են հասցնում ինքնավարությունը, բայց դժվարացնում են ընդհանուր թարմացումները և հետևողականությունը։ Որևէ մեկն ընտրելուց առաջ հարցրեք՝ արդյոք ընդհանրապես micro-frontend-ներ պետք են. մոտ երեք-չորսից պակաս ֆրոնտենդ թիմի դեպքում լավ կառուցված մոդուլային մոնոլիտը՝ թղթապանակների հստակ սեփականությամբ, lint-ով պարտադրված սահմաններով և արագ CI-ով, սովորաբար տալիս է օգուտները՝ առանց run-time բարդության։
Օգտագործեք, երբ
- Մի քանի ինքնավար թիմեր (հաճախ չորս կամ ավելի), որոնք աշխատում են մեկ պրոդուկտի վրա և արգելափակում միմյանց թողարկումներով։
- Աստիճանական միգրացիա հին ֆրոնտենդ ֆրեյմվորքից նորին՝ ոլորտ առ ոլորտ։
- Պրոդուկտներ, որոնք հավաքված են առանձին տիրապետվող ոլորտներից, օրինակ՝ պորտալ, որը միավորում է տարբեր բաժինների գործիքները։
- Երբ ինքնուրույն deploy-ը և ձախողումների մեկուսացումը արժեն լրացուցիչ ենթակառուցվածք և governance։
Մի՛ օգտագործեք, երբ
- Փոքր կամ միայնակ թիմեր. overhead-ը գերազանցում է ցանկացած օգուտ, և մոդուլային մոնոլիտն ավելի պարզ է։
- Արտադրողականության համար կրիտիկական սպառողական էջեր, որտեղ լրացուցիչ JavaScript-ը, կրկնվող կախվածությունները և waterfall-ները վնասում են կոնվերսիաներին։
- Երբ իրական խնդիրը դանդաղ CI-ն կամ խճճված կոդն է, որոնք ավելի լավ կառուցվածքն ու գործիքները ավելի էժան են լուծում։
Այլընտրանքներ
- Մոդուլային մոնոլիտ ֆրոնտենդ
Մեկ հավելված և մեկ deploy՝ խիստ ներքին մոդուլային սահմաններով և սեփականությամբ. թիմերի մեծ մասի համար ամենապարզը։
- Monorepo Nx / Turborepo-ով
Ընդհանուր գործիքներ և միայն ազդված մասերի build-եր. կարող է մնալ մեկ հավելված կամ հետագայում աճել micro-frontend-ների։
- Next.js Multi-Zones
Մի քանի Next.js հավելված մատուցում են տարբեր URL ուղիներ մեկ դոմենի տակ. route-ի մակարդակով բաժանում՝ առանց run-time federation-ի։
- Server-side կոմպոզիցիա / island-ներ
HTML-ը կազմեք սերվերում կամ edge-ում. լավ է բովանդակությամբ ծանր էջերի համար։
Լավագույն պրակտիկաներ
- Բաժանեք ըստ բիզնես դոմենի և թիմի սեփականության, երբեք ըստ տեխնիկական շերտի։
- Նախընտրեք route-ի մակարդակով սեփականություն. մեկ էկրանի վրա բազմաթիվ թիմերի կոմպոնենտներ խառնելը բազմապատկում է համակարգումը։
- Կիսեք որքան հնարավոր է քիչ. ֆրեյմվորքի singleton-ները և դիզայն համակարգը, ոչ թե բիզնես տրամաբանությունը կամ գլոբալ store-երը։
- Յուրաքանչյուր remote փաթաթեք error boundary-ով և fallback-ով, և սխալները մոնիտորինգ արեք ըստ micro-frontend-ի։
- Remote-ների URL-ները պահեք runtime կոնֆիգուրացիայում, որպեսզի թիմերը կարողանան ինքնուրույն deploy և rollback անել։
- Տարբերակավորեք shell-ի և remote-ների միջև պայմանագրերը և CI-ում թեստավորեք դրանք contract կամ ինտեգրացիոն թեստերով։
- Կազմված էջի համար սահմանեք performance budget և չափեք իրական օգտատիրոջ փորձառությունը, ոչ թե յուրաքանչյուր մասը առանձին։
Տարածված սխալներ
- Micro-frontend-ներ ընդունելը փոքր թիմի համար, որովհետև մասշտաբավորվող է հնչում, հետո խեղդվելը կոնֆիգուրացիայի մեջ։
- React-ի մի քանի օրինակ կամ դիզայն համակարգի տարբեր տարբերակներ բեռնելը նույն էջում։
- Ընդհանուր գլոբալ state store, որը բոլոր թիմերին կրկին կապում է իրար։
- Fallback-ի բացակայություն, երբ remote-ը ձախողվում է, ինչի պատճառով մեկ թիմի կոտրված deploy-ը գցում է ամբողջ կայքը։
- Յուրաքանչյուր թիմ ընտրում է տարբեր ֆրեյմվորք՝ առանց միգրացիայի ուժեղ պատճառի, բազմապատկելով bundle-ի չափը և աշխատանքի ընդունման կարիքները։
Խորը վերլուծություն
08Ընդհանուր կախվածություններ և տարբերակների համաձայնեցում
Module Federation-ի share scope-ը run-time ռեեստր է, որտեղ յուրաքանչյուր բեռնված build գրանցում է իր բերած ընդհանուր փաթեթները՝ տարբերակներով։ Երբ մոդուլին React է պետք, runtime-ը ընտրում է requiredVersion-ին բավարարող տարբերակ՝ նախընտրելով ամենաբարձր հասանելին, իսկ singleton: true-ն պարտադրում է ճիշտ մեկ instance, ինչը պահանջում են React-ը, React DOM-ը, router-ները և context-ի վրա հիմնված գրադարանները, որովհետև երկու օրինակը կոտրում է hook-երը և context-ը։ Եթե համատեղելի տարբերակ չկա, singleton-ը զգուշացում է լոգավորում և միևնույն է օգտագործում բեռնվածը, ինչը կարող է նուրբ բագեր առաջացնել. strictVersion-ը դա վերածում է սխալի։ eager: true-ն ընդհանուր մոդուլը փաթեթավորում է սկզբնական chunk-ի մեջ, ինչը խուսափում է async սահմանից, բայց մեծացնում է չափը։ Սովորական pattern-ը async bootstrap-ն է. entry ֆայլը կատարում է import('./bootstrap'), որպեսզի ընդհանուր մոդուլները համաձայնեցվեն մինչև հավելվածի գործարկումը։
// src/index.ts (entry)
import("./bootstrap");
// src/bootstrap.tsx
import { createRoot } from "react-dom/client";
import { App } from "./App";
createRoot(document.getElementById("root")!).render(<App />);
// shared config with strict checks for critical libs
// shared: {
// react: { singleton: true, requiredVersion: "^19.0.0", strictVersion: true },
// "@acme/design-system": { singleton: true, requiredVersion: "^4.2.0" },
// }
09Deploy, discovery և rollback-ներ
Ինքնուրույն deploy-ի համար shell-ին պետք է եղանակ՝ յուրաքանչյուր remote-ի ընթացիկ տարբերակը գտնելու։ URL-ները shell-ի build-ում hard-code անելը կապում է deploy-երը։ Ավելի լավ է. յուրաքանչյուր remote հրապարակում է անփոփոխ, տարբերակավորված ռեսուրսներ (product/1.14.2/mf-manifest.json) CDN-ում, իսկ փոքր discovery փաստաթուղթը կամ ծառայությունը remote-ների անունները համապատասխանեցնում է ակտիվ տարբերակին ըստ միջավայրի։ Shell-ը կարդում է այն գործարկման ժամանակ, կամ Module Federation-ի runtime plugin-ները դինամիկ լուծում են remote-ները։ Rollback-ը նշանակում է քարտեզը ուղղել նախորդ տարբերակին՝ առանց որևէ բան վերակառուցելու։ Սա հնարավոր է դարձնում նաև canary թողարկումներ և թեստավորման համար ըստ օգտատիրոջ override-ներ։ Discovery փաստաթուղթը քեշավորեք կարճ, իսկ ռեսուրսներին տվեք երկար քեշի ժամկետ, որովհետև դրանց ուղիները տարբերակավորված են։
// remotes.json served by a config endpoint (short cache)
{
"product": "https://cdn.example.com/product/1.14.2/mf-manifest.json",
"checkout": "https://cdn.example.com/checkout/3.2.0/mf-manifest.json"
}
// shell bootstrap with the MF runtime
import { init } from "@module-federation/enhanced/runtime";
const remotes = await (await fetch("/config/remotes.json")).json();
init({
name: "shell",
remotes: Object.entries(remotes).map(([name, entry]) => ({ name, entry: entry as string })),
});
10Մեկուսացում. CSS, գլոբալներ և ձախողումներ
Run-time կոմպոզիցիան մի քանի թիմերի կոդը դնում է նույն փաստաթղթում, ուստի մեկուսացումը պետք է նախագծել։ CSS. մի remote-ի գլոբալ selector-ները կարող են վերաոճավորել մյուսը. օգտագործեք CSS Modules կամ ֆրեյմվորքի scoped ոճերը, ցանկացած գլոբալ class-ի նախածանց տվեք և reset-ները պահեք միայն shell-ում։ Web Components-ը Shadow DOM-ով տալիս են ոճերի կոշտ սահմաններ՝ թեմավորման դժվարացման գնով (օգտագործեք CSS custom properties, որոնք թափանցում են shadow սահմանը)։ JavaScript գլոբալներ. երբեք remote-ում patch մի արեք window-ը կամ prototype-ները, և ցանկացած storage key-ի namespace տվեք։ Ձախողումներ. error boundary յուրաքանչյուր remote-ի համար, timeout-ներ remote-ների բեռնման վրա և fallback UI-ը էջը պահում են օգտագործելի։ Սխալներն ու արտադրողականության մետրիկաները tag արեք remote-ի անունով և տարբերակով, որպեսզի on-call ինժեներները իմանան, թե որ թիմին զանգահարել։
11Server-side կոմպոզիցիա և SSR
Client-side federation-ն աշխատում է login-ի հետևում գտնվող հավելվածների համար, բայց հանրային էջերին պետք է արագ first paint և SEO։ Տարբերակներ. route-ի մակարդակի սերվերային հավելվածներ մեկ դոմենի հետևում (Next.js Multi-Zones կամ reverse proxy, որը /checkout-ն ուղղորդում է մի հավելվածի, իսկ /blog-ը՝ մյուսին), ինչը պարզ է և հուսալի, բայց նշանակում է zone-երի միջև էջի ամբողջական բեռնումներ. HTML ֆրագմենտների edge կամ սերվերային կոմպոզիցիա, որտեղ layout ծառայությունը զուգահեռ բերում է ֆրագմենտները յուրաքանչյուր թիմից և stream անում դրանք, կամ federated SSR, որտեղ Module Federation-ը remote մոդուլները բեռնում է նաև Node.js սերվերում, ինչը հզոր է, բայց բարդ շահագործման համար՝ սերվերային քեշավորման և հիշողության հարցերով։ Հանրային կայքերի մեծ մասի համար route-ի մակարդակի zone-երը գումարած ընդհանուր դիզայն համակարգն ու header փաթեթը պրագմատիկ պատասխանն են։
# nginx: route-level composition under one domain
location /checkout/ { proxy_pass http://127.0.0.1:3002; }
location /account/ { proxy_pass http://127.0.0.1:3003; }
location / { proxy_pass http://127.0.0.1:3001; } # main shop
Իրական իրավիճակներ
Հին AngularJS ադմինի միգրացիա React
Խնդիր
Մեծ AngularJS ադմին վահանակն այլևս չի աջակցվում։ Ամբողջական վերագրումը կտևի մեկ տարի և կսառեցնի նոր հնարավորությունները։ Բիզնեսը նոր էկրաններ ուզում է React-ով հիմա։
Ինչպես լուծել
Օգտագործեք strangler pattern-ը shell-ով։ Առջևում դրեք բարակ shell, որը տիրապետում է routing-ին և layout-ին։ Հին route-երի համար mount արեք legacy հավելվածը, իսկ նոր և վերագրված էկրանները կառուցեք որպես React micro-frontend-ներ՝ գրանցված իրենց route-երի համար և բեռնված Module Federation-ով կամ single-spa-ով։ Authentication-ը կիսեք փոքր shell API-ի միջոցով։ Միգրացրեք էկրան առ էկրան՝ սկսելով ամենաարժեքավոր կամ ամենահաճախ փոխվողներից, և ջնջեք legacy route-երը, երբ դրանք փոխարինվում են։ Երբ վերջին legacy էկրանը վերանա, shell-ը կարող է վերածվել սովորական React հավելվածի, ուստի micro-frontend-ները հավերժ չեք պահում, եթե թիմերին դրանք պետք չեն։
Մեկ թիմի deploy-ը կոտրեց ամբողջ կայքը
Խնդիր
Remote-ը թողարկեց bundle, որը բեռնման ժամանակ սխալ է նետում։ Քանի որ shell-ը այն import է անում վերին մակարդակում, յուրաքանչյուր էջ ցույց է տալիս սպիտակ էկրան։
Ինչպես լուծել
Անմիջապես rollback արեք՝ discovery քարտեզն ուղղելով remote-ի նախորդ տարբերակին։ Հետո ամրապնդեք shell-ը. յուրաքանչյուր remote ծույլ բեռնեք միայն իր route-երում, փաթաթեք այն error boundary-ով՝ օգտակար fallback-ով, և ավելացրեք բեռնման timeout։ Ավելացրեք post-deploy smoke test, որը կազմված էջը բեռնում է headless բրաուզերում՝ նախքան նոր տարբերակի առաջխաղացումը, և մոնիտորինգ արեք ըստ remote-ի tag արված սխալները alert-ներով։ Տարբերակավորված անփոփոխ ռեսուրսներով և run-time discovery-ով rollback-ը դառնում է կոնֆիգի փոփոխություն՝ չափվող վայրկյաններով։
Փոքր թիմը մտածում է micro-frontend-ների մասին
Խնդիր
Startup-ը՝ հինգ ֆրոնտենդ ծրագրավորողով մեկ պրոդուկտի վրա, նկատում է, որ իր Next.js հավելվածը դառնում է խառնաշփոթ, իսկ CI-ը՝ դանդաղ։ Ինչ-որ մեկն առաջարկում է բաժանել micro-frontend-ների։
Ինչպես լուծել
Նախ լուծեք իրական ցավը։ Վերակազմակերպեք հավելվածը feature մոդուլների՝ հստակ հանրային ինտերֆեյսներով, սահմանները պարտադրեք ESLint կանոններով կամ Nx module boundaries-ով և ավելացրեք CODEOWNERS յուրաքանչյուր feature-ի համար։ Արագացրեք CI-ը քեշավորմամբ և միայն ազդված մասերի թեստերով Turborepo-ի կամ Nx-ի միջոցով։ Դիզայն համակարգը հանեք ներքին փաթեթի մեջ։ Micro-frontend-ներին վերադարձեք միայն այն ժամանակ, երբ մի քանի ինքնուրույն թիմեր իսկապես արգելափակում են միմյանց թողարկումներով։ Հինգ ծրագրավորողի դեպքում run-time կոմպոզիցիան կավելացնի ենթակառուցվածքի, տարբերակավորման և արտադրողականության խնդիրներ՝ առանց կոդի կառուցվածքի խնդիրը լուծելու։
Արագ ուղեցույց
- Մոդուլ տրամադրել (MF)
exposes: { "./ProductPage": "./src/ProductPage.tsx" }- Remote հայտարարել (MF manifest)
remotes: { product: "product@https://cdn.example.com/product/mf-manifest.json" }- React-ը կիսել մեկ անգամ
shared: { react: { singleton: true, requiredVersion: "^19.0.0" } }- Async bootstrap entry ֆայլում
import("./bootstrap");- Remote-ի ծույլ բեռնում (React)
const Page = lazy(() => import("product/ProductPage"))- Հավելվածների միջև իրադարձություն
window.dispatchEvent(new CustomEvent("cart:updated", { detail }))- Rsbuild-ի MF plugin
npm i -D @module-federation/rsbuild-plugin- Միայն ազդված մասերի build-եր (Nx)
npx nx affected -t build test
Հարցեր և պատասխաններ
Q1Ի՞նչ խնդիր են իրականում լուծում micro-frontend-ները։
Դրանք ավելի շատ կազմակերպչական խնդիր են լուծում, քան տեխնիկական։ Երբ բազմաթիվ թիմեր աշխատում են մեկ ֆրոնտենդ կոդային բազայում, նրանք կիսում են release train, pipeline և կախվածությունների ծառ, ուստի արգելափակում են միմյանց, և սեփականությունը մշուշոտվում է։ Micro-frontend-ները թույլ են տալիս յուրաքանչյուր թիմի ամբողջությամբ տիրապետել բիզնես ոլորտին և deploy անել ինքնուրույն՝ ձախողումները մեկուսացված իրենց մասում։ Դրանք նաև հնարավոր են դարձնում ֆրեյմվորքների միջև աստիճանական միգրացիան։ Դրանք մեկ թիմը ավելի արագ չեն դարձնում, ինքնին չեն բարելավում արտադրողականությունը և ավելացնում են run-time ինտեգրացիայի, տարբերակավորման և հետևողականության մարտահրավերներ։ Ուստի առաջին հարցը միշտ է. արդյո՞ք մենք ունենք թիմերի մասշտաբավորման խնդիր։
Q2Ի՞նչ է Module Federation-ը։
Module Federation-ը թույլ է տալիս առանձին կառուցված և deploy արված JavaScript հավելվածներին run time-ում կոդ բեռնել միմյանցից։ Remote հավելվածը մոդուլներ է տրամադրում փոքր entry կամ manifest ֆայլի միջոցով. host հավելվածը հայտարարում է այդ remote-ը և ծույլ կերպով import անում դրա մոդուլները, կարծես դրանք լոկալ փաթեթներ լինեին։ Ընդհանուր կախվածությունները, ինչպես React-ը, հայտարարվում են որպես singleton-ներ, ուստի բրաուզերը բեռնում է մեկ համատեղելի օրինակ՝ յուրաքանչյուր թիմի համար մեկի փոխարեն։ Այն սկսվել է webpack 5-ում. Module Federation 2.0-ն ինքնուրույն runtime է՝ webpack-ի, Rspack-ի, Rsbuild-ի և Vite-ի plugin-ներով, գումարած manifest-ներ, type hint-եր և devtools։
Q3Ինչպե՞ս են micro-frontend-ները հաղորդակցվում։
Որքան հնարավոր է քիչ և բացահայտ։ URL-ը լավագույն ընդհանուր state-ն է. եթե checkout-ին պետք է ապրանքի ID-ն, այն կարդում է route-ը։ Shell-ը կարող է props կամ փոքր context օբյեկտ փոխանցել remote-ը mount անելիս։ Ծանուցումների համար, ինչպես զամբյուղի քանակի փոփոխությունը, լավ են աշխատում բրաուզերի custom event-ները կամ փոքրիկ տիպավորված event bus-ը։ Յուրաքանչյուր micro-frontend իր տվյալները բերում է բեքենդ API-ներից, որոնք իրական ճշմարտության աղբյուրն են։ Խուսափում եմ ընդհանուր գլոբալ store-ից, որովհետև այն թիմերին ստիպում է համաձայնեցնել դրա կառուցվածքն ու տարբերակը, ինչը վերացնում է բաժանման իմաստը։
Q4Ինչպե՞ս եք UI-ը հետևողական պահում թիմերի միջև։
Ընդհանուր դիզայն համակարգով, որին վերաբերվում են որպես պրոդուկտի. design token-ներ գույնի, տարածության և տիպոգրաֆիայի համար, տարբերակավորված կոմպոնենտների գրադարան, Storybook փաստաթղթավորում և ներկառուցված accessibility։ Թիմերն այն օգտագործում են որպես կախվածություն, իդեալում՝ որպես federated singleton կամ տարբերակների նեղ միջակայքերով, որպեսզի էջը չբեռնի մի քանի տարբերակ։ Ոճերի մեկուսացումը CSS Modules-ի, scoped նախածանցների կամ Shadow DOM-ի միջոցով կանխում է մի թիմի CSS-ի արտահոսքը մյուսի ոլորտ։ Governance-ը նույնպես կարևոր է. փոքր platform թիմը տիրապետում է դիզայն համակարգին և shell-ին, վերանայում է պայմանագրերը և թարմացման ուղիները պահում հարթ։
Q5Որո՞նք են արտադրողականության ռիսկերը։
Մի քանի ինքնուրույն կառուցված bundle-ը կարող է նշանակել կրկնվող գրադարաններ, լրացուցիչ ցանցային հարցումներ և բեռնման waterfall-ներ. բեռնվում է shell-ը, հետո բերում է remote-ի manifest-ը, հետո նրա chunk-երը, հետո նրա տվյալները։ Յուրաքանչյուր թիմ կարող է նաև ուղարկել իր polyfill-ներն ու utility-ները։ Մեղմացումներ. ֆրեյմվորքի կախվածությունները կիսեք որպես singleton-ներ, preload արեք remote-ները հավանական հաջորդ route-երի համար, հնարավորության դեպքում server-render կամ prerender արեք, սահմանեք էջի մակարդակի performance budget և մոնիտորինգ արեք կազմված էջի Core Web Vitals-ը։ Route-ի մակարդակով բաժանումը լավագույն արդյունքն է տալիս, որովհետև օգտատերը բեռնում է միայն այն էջի remote-ները, որտեղ գտնվում է։
Q6Micro-frontend-նե՞ր, թե՞ monorepo։
Դրանք տարբեր առանցքներ են։ Monorepo-ն այն մասին է, թե որտեղ է ապրում կոդը. micro-frontend-ները՝ թե ինչպես է UI-ը deploy արվում և կազմվում։ Կարող եք ունենալ մեկ հավելված monorepo-ում, micro-frontend-ներ monorepo-ում կամ micro-frontend-ներ բազմաթիվ ռեպոզիտորիաներում։ Ընկերությունների մեծ մասի համար monorepo-ն Nx-ով կամ Turborepo-ով, փաթեթների հստակ սահմաններով և միայն ազդված մասերի build-երով լուծում է համագործակցության ցավը՝ առանց run-time կոմպոզիցիայի։ Եթե թիմերին հետագայում պետք լինի լիովին ինքնուրույն deploy, նույն monorepo-ն կարող է արտադրել առանձին deploy արվող micro-frontend-ներ՝ ընդհանուր գործիքներով և դիզայն համակարգով, ինչը շատ ավելի հեշտ է, քան բազմաթիվ ռեպոզիտորիաներով սկսելը։
Սենիոր մակարդակի հարցեր
S1Ինչպե՞ս եք որոշում micro-frontend-ների միջև սահմանները։
Սկսում եմ թիմերի կառուցվածքից և բիզնես կարողություններից՝ օգտագործելով domain-driven design. bounded context-ները, ինչպես catalogue-ը, checkout-ը և account-ը, հաճախ լավ համապատասխանում են micro-frontend-ներին և, իդեալում, նույն թիմին պատկանող բեքենդ ծառայություններին։ Լավ սահմանն ունի բարձր ներքին համախմբվածություն, արտաքինում՝ քիչ և կայուն փոխազդեցություններ, և փոփոխություններ, որոնք հազվադեպ են պահանջում, որ երկու թիմ միասին deploy անեն։ Route-ի մակարդակի սահմանները նախընտրելի են, որովհետև նվազագույնի են հասցնում ընդհանուր էկրանային տարածքը։ Վատ բաժանման նախանշաններն են մասերի միջև իրադարձությունների ակտիվ տրաֆիկը, հաճախակի համակարգված թողարկումները և ամենուր ընդհանուր state-ը։ Գործում է Conway-ի օրենքը. եթե կազմակերպական կառուցվածքը չի համապատասխանում բաժանմանը, ճարտարապետությունը կշեղվի դեպի կազմակերպական կառուցվածքը, ուստի դրանք համապատասխանեցնում եմ գիտակցաբար։
S2Module Federation, import map-եր, թե՞ Web Components. ինչպե՞ս եք ընտրում։
Module Federation-ը ամենահզորն է նույն ֆրեյմվորքով աշխատող թիմերի համար. կախվածությունների կիսում տարբերակների համաձայնեցմամբ, lazy loading և bundler-ի ինտեգրացիա, բայց այն ձեզ կապում է աջակցվող bundler-ներին և իր runtime-ին։ Import map-երը native ES մոդուլներով հիմնված են ստանդարտների վրա և անկախ են bundler-ից, և single-spa-ն կամ նմանատիպ loader-ները կառուցված են դրանց վրա. կախվածությունները կիսելու համար պետք է դրանք externalize անել համաձայնեցված URL-ների, ինչը ավելի շատ կարգապահություն է պահանջում։ Web Components-ը տալիս են ֆրեյմվորքից անկախ, ինկապսուլացված պայմանագիր՝ attribute-ներ ներս և իրադարձություններ դուրս, ինչը իդեալական է, երբ թիմերն օգտագործում են տարբեր ֆրեյմվորքներ, կամ սպառողը CMS է, բայց յուրաքանչյուր կոմպոնենտ կարող է ուղարկել իր ֆրեյմվորքի runtime-ը, իսկ SSR-ը գումարած ձևերի ինտեգրացիան ավելի դժվար են։ Հաճախ համատեղում եմ դրանք. Module Federation՝ բեռնման համար, Web Component wrapper-ներ՝ միայն ֆրեյմվորքների միջև սահմաններում։
S3Ինչպիսի՞ governance է պետք micro-frontend ճարտարապետությանը։
Ինքնավարությունն առանց governance-ի դառնում է քաոս։ Փոքր platform թիմը տիրապետում է shell-ին, դիզայն համակարգին, ընդհանուր կախվածությունների policy-ին և deploy-ի գործիքներին։ Թիմերը համաձայնում են կանոնների կարճ ցուցակի շուրջ. թույլատրված ֆրեյմվորքներ և major տարբերակներ, singleton կախվածություններ, performance budget-ներ ըստ route-ի, accessibility ստանդարտներ, սխալների և analytics-ի կոնվենցիաներ և shell-ի ու remote-ների միջև պայմանագրերի տարբերակավորում։ Այս կանոնները պարտադրվում են գործիքներով, ոչ թե փաստաթղթերով. CI-ը ստուգում է bundle-ի չափը, ընդհանուր տարբերակները և contract թեստերը, իսկ dashboard-ները ցույց են տալիս Core Web Vitals-ը և սխալներն ըստ remote-ի։ Կա նաև կյանքի ցիկլի գործընթաց՝ ընդհանուր ֆրեյմվորքը բոլոր թիմերում թարմացնելու համար՝ ժամկետներով։ Նպատակն այն է, որ թիմերը լինեն անկախ առաքման մեջ, բայց հետևողական՝ օգտատիրոջ փորձառության մեջ։
Բառարան
- Micro-frontend
- Վեբ UI-ի ինքնուրույն կառուցված և deploy արված մաս, որին տիրապետում է մեկ թիմ և որը կազմվում է ավելի մեծ հավելվածի մեջ։
- Shell (host)
- Կոնտեյներ հավելված, որը տրամադրում է layout, routing և ընդհանուր ծառայություններ և բեռնում micro-frontend-ները։
- Remote
- Module Federation-ում build, որը մոդուլներ է տրամադրում այլ հավելվածներին՝ run time-ում բեռնելու համար։
- Module Federation
- Bundler-ի և runtime-ի հնարավորություն, որը թույլ է տալիս առանձին deploy արված build-երին կիսել և բեռնել միմյանց մոդուլները։
- Singleton կախվածություն
- Ընդհանուր գրադարան, որը պարտադրաբար բեռնվում է էջում միայն մեկ անգամ, օրինակ՝ React-ը։
- Import map
- Բրաուզերի հնարավորություն, որը մոդուլների անունները համապատասխանեցնում է URL-ների՝ հնարավոր դարձնելով native ES մոդուլների կոմպոզիցիա։
- Multi-Zones
- Next.js pattern, որտեղ առանձին հավելվածներ մատուցում են տարբեր URL ուղիներ մեկ դոմենի տակ։
- Strangler pattern
- Legacy համակարգի աստիճանական փոխարինում՝ դրա մասերը նոր կոդին ուղղորդելով, մինչև հին համակարգը վերանա։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։