Skip to content

Latest commit

 

History

History
357 lines (287 loc) · 7.83 KB

05. 微前端框架开发 - 框架初长成.md

File metadata and controls

357 lines (287 loc) · 7.83 KB

微前端框架开发 - 框架初长成

中央控制器 - 主应用开发

主应用开发

子应用注册

subApps.js

// 子应用列表
let list = []

export const getList = () => list

export const setList = (data) => {
  if (Array.isArray(data)) {
    list = data;
    return
  }

  list.push(data)
}
export const registerMicroApps = (apps) => {
  // 注册子应用
  setList(apps)
}

微前端框架 - 路由拦截

rewriteRouter.js

import { patchRouter } from '../util';
import { turnApp } from './routerHandler';

export const rewriteRouter = () => {
  window.history.pushState = patchRouter(window.history.pushState, 'micro_push');
  window.history.replaceState = patchRouter(window.history.replaceState, 'micro_replace');

  // 添加路由跳转事件监听
  window.addEventListener('micro_push', turnApp);
  window.addEventListener('micro_replace', turnApp);
  window.onpopstate = async function () {
    await turnApp()
  }
}

util/index.js

// 改变全局事件状态
export const patchRouter = (event, ListerName) => {
  return function () {
    // 创建一个自定义事件
    const e = new Event(ListerName);
    // 让event来代替本函数执行
    event.apply(this, arguments);
    // 通过dispatchEvent来触发自定义事件
    window.dispatchEvent(e);
  };

routerHandler.js

export const turnApp = async () => {
    console.log("路由被切换了...")
}

微前端框架 - 获取首个子应用

start.js

export const start = async () =>  {
  // 获取子应用列表
  const appList = getList()

  if (!appList.length) {
    throw Error('子应用列表为空,请查看是否正确注册');
  } else {

    // 跳转到第一个子应用
    const app = currentApp();

    if (app) {
      const { pathname, hash } = window.location
      const url = pathname + hash

      window.history.pushState(url, app.name, url || app.activeRule)
    }

    // 将当前子应用做标记
    window.__CURRENT_SUB_APP__ = app.activeRule;
  }
}

util/index.js

// 获取当前应用
export const currentApp = () => {
  const currentRouter = window.location.pathname.match(/(\/\w+)/)[0];

  return filterApp('activeRule', currentRouter);
};

export const filterApp = (key, rule) => {
  const currentApp = getList().filter(app => app[key] === rule);

  return currentApp.length ? currentApp[0] : false;
};

微前端框架 - 主应用生命周期

mainLifecycles.js

let mainLifecycle = {}

export const getMainLifecycle = () => mainLifecycle

export const setMainLifecycle = (data) => {
  mainLifecycle = data
}
  // 注册子应用
  registerMicroApps(
    leftNav.navList,
    // 生命周期
    {
      beforeLoad: [
        app => {
          app.loading.openLoading()
          // 每次改动,都将头部和底部显示出来,不需要头部和底部的页面需要子应用自己处理
          headerState.changeHeader(true)
          footerState.changeFooter(true)
          console.log('开始加载 -- ', app.name);
        },
      ],
      mounted: [
        app => {
          console.log('加载完成 -- ', app.name);
          setTimeout(() => {
            app.loading.closeLoading()
          }, 200)
        },
      ],
      destoryed: [
        app => {
          console.log('卸载完成 -- ', app.name);
        },
      ],
    },
    {

    }
  );

微前端框架 - 微前端生命周期

util/index.js

// 查看当前路由是否有变化
export const isTurnChild = () => {
  const { pathname, hash } = window.location
  const url = pathname + hash

  // 当前路由无改变。
  const currentPrefix = url.match(/(\/\w+)/g)

  if (
    currentPrefix &&
    (currentPrefix[0] === window.__CURRENT_SUB_APP__) &&
    hash === window.__CURRENT_HASH__
  ) {
    return false;
  }

  window.__ORIGIN_APP__ = window.__CURRENT_SUB_APP__;

  const currentSubApp = window.location.pathname.match(/(\/\w+)/)

  if (!currentSubApp) {
    return false
  }
  // 当前路由以改变,修改当前路由
  window.__CURRENT_SUB_APP__ = currentSubApp[0];

  // 判断当前hash值是否改变
  window.__CURRENT_HASH__ = hash

  return true;
};

// 根据 路由 查找子应用
export const findAppByRoute = (router) => {
  return filterApp('activeRule', router);
};

// 根据 name 查找子应用
export const findAppByName = (name) => {
  return filterApp('name', name);
};

export const filterApp = (key, rule) => {
  const currentApp = getList().filter(app => app[key] === rule);

  return currentApp.length ? currentApp[0] : false;
};

routerHandler.js

import { lifecycle } from "../lifecycle/lifecycle"
import { isTurnChild } from "../util"
export const turnApp = async () => {
  // 查看当前路由是否有变化
  if (isTurnChild()) {
    // 路由变化,同步修改子应用
    await lifecycle()
  }
}

获取需要展示的页面 - 加载和解析html

fetchResources.js

// 获取页面资源
export const fetchUrl = url => fetch(url).then(async res => res.text())
const div = document.createElement('div');
div.innerHTML = await fetchUrl(url);

加载和解析js

htmlLoader.js

import {fetchUrl} from '../util/fetchResources';
import {sandbox} from '../sandbox/sandbox';
import {findAppByName} from '../util';

const cache = {};

// 解析html
export const parseHtml = async (url, appName) => {
  if (cache[appName]) {
    return cache[appName];
  }
  const div = document.createElement('div');
  let scriptsArray = [];

  div.innerHTML = await fetchUrl(url);

  const [scriptUrls, scripts, elements] = getResources(div, findAppByName(appName));
  const fetchedScript = await Promise.all(scriptUrls.map(url => fetchUrl(url)));

  scriptsArray = scripts.concat(fetchedScript);
  cache[appName] = [elements, scriptsArray];

  return [elements, scriptsArray];
}

// 解析 js 内容
export const getResources = (root, app) => {
  const scriptUrls = [];
  const scripts = [];

  function deepParse(element) {
    const children = element.children;
    const parent = element.parentNode;

    // 处理位于 link 标签中的 js 文件
    if (element.nodeName.toLowerCase() === 'script') {
      const src = element.getAttribute('src');
      if (!src) {
        // 直接在 script 标签中书写的内容
        let script = element.outerHTML;
        scripts.push(script);
      } else {
        if (src.startsWith('http')) {
          scriptUrls.push(src);
        } else {
          // fetch 时 添加 publicPath
          scriptUrls.push(`http:${app.entry}/${src}`);
        }
      }

      if (parent) {
        let comment = document.createComment('此 js 文件已被微前端替换');
        // 在 dom 结构中删除此文件引用
        parent.replaceChild(comment, element);
      }
    }
    // 处理位于 link 标签中的 js 文件
    if (element.nodeName.toLowerCase() === 'link') {
      const href = element.getAttribute('href');
      if (href.endsWith('.js')) {
        if (href.startsWith('http')) {
          scriptUrls.push(href);
        } else {
          // fetch 时 添加 publicPath
          scriptUrls.push(`http:${app.entry}/${href}`);
        }
      }
    }
    for (let i = 0; i < children.length; i++) {
      deepParse(children[i]);
    }
  }
  deepParse(root);

  return [scriptUrls, scripts, root.outerHTML];
}

// 加载和渲染html
export const htmlLoader = async (app) => {
  const {
    container: cantainerName, entry, name
  } = app
  let [dom, scriptsArray] = await parseHtml(entry, name);

  let container = document.querySelector(cantainerName);
  if (!container) {
    throw Error(` ${name} 的容器不存在,请查看是否正确指定`);
  }

  container.innerHTML = dom;
  scriptsArray.map((item) => {
    sandbox(item, name);
  });
}
export const performScriptForFunction = (script) => {
  new Function(script).call(window, window);
}

export const performScriptForEval = (script) => {
  eval(script)
}