在软件开发中,逐渐出现了类,模块化,组件化,设计模式等来解耦和拆分咱们的代码,使得代码更易读,易维护。而微前端架构其实也是一种新的思想来帮助咱们更好的拆分一些如今方式没法解决的问题而已。html
相似与操做系统,将系统的实现与系统的基本操做规则区分开来。将核心功能模块化,划分红几个独立的进程,各自运行,全部的服务进程都运行在不一样的地址空间,让服务各自独立。前端
如上图所示:微前端落地到浏览器,浏览器将承载一个html页面,在页面中安装,启动相应的服务。web
我的感受除去微前端的一些技术实现,它主要难点就在于如何拆分应用,在拆分的同时还需结合团队规模等问题:npm
并非全部应用都适合使用微前端架构,在一个简单的单体应用中使用,反而拔苗助长。
微前端主要应用在大型的互联网应用,该应用可能具有几个特色:系统庞大到不少人去开发,页面数量达到某个量级,在这种状况下可能会致使系统难以维护,代码量逐渐增大,同时也使得协做方面难以管控,包括测试,回归,而且在工程化方面编译显的耗时。总结有如下几点状况bootstrap
和微服务同样,微前端的独立部署是关键。减小服务间的耦合性,不管前端代码部署在哪里,每一个微前端都有本身持续交付pipeline,进行构建,测试,部署到生产环境中。最后将多个子系统集成到主系统中。设计模式
基座工程:数组
子应用:promise
路由控制层主要监控路由的变动,经过路由变动来控制子系统是否须要加载。子系统路由发生变化首先会有主系统拦截路由变动时间,决定是否加载子系统,若是路由不须要切换子系统,则将该事件交还给子系统处理。浏览器
应用注册其实就是相似于平时的帐号注册,须要填写一些基本信息,而在微前端中所指的应用注册主要是指app名称,以及对应子系统配置文件的url。而在single-spa中registerApplication主要包含三个参数,appName,app,activeWhen缓存
在主应用中会注册多个子应用,而这些子应用的信息及状态会进行保存和管理。
注册对应的子系统,当路由规则匹配到某个子系统的时候会先去加载该子系统的manifest文件来获取该子系统的信息,经过该文件去加载对应的子系统。
single-spa执行队列有两个入口,一是经过监听路由的变化,二是register函数。
每次触发会先判断是否已启动,若是未启动则执行loadApps去加载须要加载的app。若是已启动则调用performAppChanges函数去mount app。
在执行期间,若是有新的app进来也就是队列发生了变动,会将新的app缓存待执行完当前的再循环执行下一次,这个操做由finishUpAndReturn这个函数内部作判断来完成。
single-spa主要有一下几个模块:
下面来大体看一下几个核心函数大体的实现
该函数接收4个参数:
export function registerApplication( appNameOrConfig, appOrLoadApp, activeWhen, customProps ) { const registration = sanitizeArguments( appNameOrConfig, appOrLoadApp, activeWhen, customProps ); apps.push( assign( { loadErrorTime: null, status: NOT_LOADED, }, registration ) ); reroute(); }
function urlReroute() { reroute([], arguments); } window.addEventListener("hashchange", urlReroute); window.addEventListener("popstate", urlReroute);
const originalAddEventListener = window.addEventListener; window.addEventListener = function(eventName, fn) { if (typeof fn === "function") { if ( routingEventsListeningTo.indexOf(eventName) >= 0 && !find(capturedEventListeners[eventName], listener => listener === fn) ) { capturedEventListeners[eventName].push(fn); return; } } return originalAddEventListener.apply(this, arguments); };
let appChangeUnderway = false, peopleWaitingOnAppChange = []; function reroute(pendingPromises = [], eventArguments) { ....... }
函数接收两个参数:
下面都是reroute函数里面的代码:
if (appChangeUnderway) { return new Promise((resolve, reject) => { peopleWaitingOnAppChange.push({ resolve, reject, eventArguments }); }); }
let wasNoOp = true; if (isStarted()) { appChangeUnderway = true; return performAppChanges(); } else { return loadApps(); }
function loadApps() { return Promise.resolve().then(() => { const loadPromises = getAppsToLoad().map(toLoadPromise); if (loadPromises.length > 0) { wasNoOp = false; } return ( Promise.all(loadPromises) .then(callAllEventListeners) .then(() => []) .catch(err => { callAllEventListeners(); throw err; }) ); }); }
function performAppChanges() { return Promise.resolve().then(() => { const unloadPromises = getAppsToUnload().map(toUnloadPromise); const unmountUnloadPromises = getAppsToUnmount() .map(toUnmountPromise) .map(unmountPromise => unmountPromise.then(toUnloadPromise)); const allUnmountPromises = unmountUnloadPromises.concat(unloadPromises); if (allUnmountPromises.length > 0) { wasNoOp = false; } const unmountAllPromise = Promise.all(allUnmountPromises); const appsToLoad = getAppsToLoad(); /* We load and bootstrap apps while other apps are unmounting, but we * wait to mount the app until all apps are finishing unmounting */ const loadThenMountPromises = appsToLoad.map(app => { return toLoadPromise(app) .then(toBootstrapPromise) .then(app => { return unmountAllPromise.then(() => toMountPromise(app)); }); }); if (loadThenMountPromises.length > 0) { wasNoOp = false; } /* These are the apps that are already bootstrapped and just need * to be mounted. They each wait for all unmounting apps to finish up * before they mount. */ const mountPromises = getAppsToMount() .filter(appToMount => appsToLoad.indexOf(appToMount) < 0) .map(appToMount => { return toBootstrapPromise(appToMount) .then(() => unmountAllPromise) .then(() => toMountPromise(appToMount)); }); if (mountPromises.length > 0) { wasNoOp = false; } return unmountAllPromise .catch(err => { callAllEventListeners(); throw err; }) .then(() => { /* Now that the apps that needed to be unmounted are unmounted, their DOM navigation * events (like hashchange or popstate) should have been cleaned up. So it's safe * to let the remaining captured event listeners to handle about the DOM event. */ callAllEventListeners(); return Promise.all(loadThenMountPromises.concat(mountPromises)) .catch(err => { pendingPromises.forEach(promise => promise.reject(err)); throw err; }) .then(finishUpAndReturn); }); }); }
能不用则不用!!!!!