Внедрение внешнего микросервиса single-spa с 0 (ниже)

Vue.js

предисловие

Предыдущая статья:Внедрение внешнего микросервиса single-spa с 0 (средний уровень)мы достиглиsingle-spa + systemJSИнтерфейсные микросервисы и идеальная конфигурация разработки и упаковки , сегодня я в основном рассказываю о деталях этого решения, иqiankunНекоторые исследования сравнения фреймворков.

Проблемы и решения решения single-spa + systemJs

single-spaТри функции жизненного циклаbootstrap,mount,unmountПредставляет инициализацию, загрузку и выгрузку соответственно.

  1. Экспорт подсистемыbootstrap,mountа такжеunmountфункция обязательна, ноunloadявляется необязательным.
  2. Каждая функция жизненного цикла должна возвращатьPromise.
  3. Если вы экспортируете массив функций (вместо одной функции), функции будут вызываться одна за другой, ожидаяpromiseВызовите следующий после разбора.

Решение проблемы загрязнения css

Мы знаем, что после деинсталляции подсистемы ее вводcssне удаляются, поэтому удаляйте их при удалении подсистемыcss, это решениеcssПодход к загрязнению, но не очень хорошо документирует то, что вводят подсистемыcss.

Мы можем решить это с идеей скиннингаcssзагрязнения, в первую очередьcss-scopedРешите 95% загрязнения стиля, и тогда глобальный стиль может вызвать загрязнение, нам нужно только использовать глобальный стильid/classПросто оберните его, чтобы эти глобальные стили были только в этомid/classэффективен в рамках.

Конкретный метод: когда подсистема загружается (mount)Дать<body>добавить специальныйid/class, то при разгрузке подсистемы (unmount) удалить этоid/class. А глобальные стили подсистемы есть только в этомid/classОн вступает в силу в пределах области действия.Если подсистема работает независимо, это должно быть только в файле входа подсистемы.index.htmlвнутри дать<body>добавить это вручнуюid/classВот и все.

код показывает, как показано ниже:

async function mount(props){
  //给body加class,以解决全局样式污染
  document.body.classList.add('app-vue-history')
}
async function unmount(props){
  //去掉body的class
  document.body.classList.remove('app-vue-history')
}

Конечно, глобальные стили, которые вы написали, тоже здесь.classпод:

.app-vue-history{
    h1{
        color: red
    }
}

js решение проблемы загрязнения

На данный момент хорошего решения нет, но оно может быть ограничено стандартами кодирования: перед уничтожением страницы очистить таймер/глобальное событие на собственной странице, а при необходимости глобальную переменную также следует уничтожить.

Как внедрить систему переключения для замены значка favicon.ico

Это относительно распространенное требование, похожее на систему, в которой необходимо вставить специальныйjs/css, а другим системам это не нужно, решение все равно при загрузке подсистемы (mount) вставьте необходимоеjs/css, когда подсистема разгружена (unmount) удалить.

const headEle = document.querySelector('head');
let linkEle = null ;
// 因为新插入的icon会覆盖旧的,所以旧的不用删除,如果需要删除,可以在unmount时再插入进来
async function mount(props){
  linkEle = document.createElement("link");
  linkEle.setAttribute('rel','icon');
  linkEle.setAttribute('href','https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/favicons/favicon.ico~tplv-t2oaga2asx-image.image');
  headEle.appendChild(linkEle);
}
async function unmount(props){
  headEle.removeChild(linkEle);
  linkEle = null;
}

Примечание. В приведенном выше примере изменен тег значка, что не влияет на загрузку страницы. Если подсистеме нужно загрузить определенный js (например, файл конфигурации) перед загрузкой страницы, необходимо написать функцию загрузки js как промис, и поставить эту периодическую функцию перед циклом, возвращаемым single-spa- вью.

Как системы взаимодействуют друг с другом

Обычно существует два способа связи между системами: настраиваемые события и локальное хранилище. Если две системы переходят друг к другу, вы можете использоватьURLпередавать данные.

Вообще говоря, одновременно не будет двух подсистем A и B. Общий обмен данными — это информация для входа в систему, а информация для входа обычно записывается в локальное хранилище. Другим распространенным сценарием является то, что подсистема изменяет информацию о пользователе, а основной системе необходимо повторно запросить информацию о пользователе.В настоящее время для связи обычно используются настраиваемые события.Подробнее о том, как работать с настраиваемыми событиями, см. пример в предыдущем статья.

Кроме того,single-spaфункция регистрацииregisterApplication, четвертый параметр может передавать данные в подсистему, но передаваемые данные должны бытьобъект.

При регистрации подсистемы:

singleSpa.registerApplication(
    'appVueHistory',
    () => System.import('appVueHistory'),
    location => location.pathname.startsWith('/app-vue-history/'),
    { authToken: "d83jD63UdZ6RS6f70D0" }
)

Подсистема (appVueHistory)Получить данные:

export function mount(props) {
  //官方文档写的是props.customProps.authToken,实际上发现是props.authToken
  console.log(props.authToken); 
  return vueLifecycles.mount(props);
}

Относительно функций жизненного цикла подсистем:

  1. функция жизненного циклаbootstrap,mount,unmountоба включают параметрыprops
  2. параметрpropsэто объект, содержащийname,singleSpa,mountParcel,customProps. Различные версии могут незначительно отличаться
  3. объект параметраcustomPropsЭто параметр, который передается при регистрации.

Как подсистема реализует поддержку активности

Проверитьsingle-spa-vueИсходный код можно найти вunmountжизненный цикл будетvueПримерdestroy(разрушен) и опустошенDOM. так реализоватьkeep-aliveКлюч лежит в подсистемеunmountНе разрушается в течение циклаvueэкземпляр и не пустойDOM,использоватьdisplay:noneчтобы скрыть подсистему. пока вmountцикла, сначала определить, существует ли подсистема, если она есть, удалить ееdisplay:noneВот и все.

нам нужно изменитьsingle-spa-vueЧасть исходного кода:

function mount(opts, mountedInstances, props) {
  let instance = mountedInstances[props.name];
  return Promise.resolve().then(() => {
    //先判断是否已加载,如果是,则直接将其显示出来
    if(!instance){
      //这里面都是其源码,生成DOM并实例化vue的部分
      instance = {};
      const appOptions = { ...opts.appOptions };
      if (props.domElement && !appOptions.el) {
        appOptions.el = props.domElement;
      }
      let domEl;
      if (appOptions.el) {
        if (typeof appOptions.el === "string") {
          domEl = document.querySelector(appOptions.el);
          if (!domEl) {
            throw Error(
              `If appOptions.el is provided to single-spa-vue, the dom element must 
                  exist in the dom. Was provided as ${appOptions.el}`
            );
          }
        } else {
          domEl = appOptions.el;
        }
      } else {
        const htmlId = `single-spa-application:${props.name}`;
        // CSS.escape 的文档(需考虑兼容性)
        // https://developer.mozilla.org/zh-CN/docs/Web/API/CSS/escape
        appOptions.el = `#${CSS.escape(htmlId)}`;
        domEl = document.getElementById(htmlId);
        if (!domEl) {
          domEl = document.createElement("div");
          domEl.id = htmlId;
          document.body.appendChild(domEl);
        }
      }
      appOptions.el = appOptions.el + " .single-spa-container";
      // single-spa-vue@>=2 always REPLACES the `el` instead of appending to it.
      // We want domEl to stick around and not be replaced. So we tell Vue to mount
      // into a container div inside of the main domEl
      if (!domEl.querySelector(".single-spa-container")) {
        const singleSpaContainer = document.createElement("div");
        singleSpaContainer.className = "single-spa-container";
        domEl.appendChild(singleSpaContainer);
      }
      instance.domEl = domEl;
      if (!appOptions.render && !appOptions.template && opts.rootComponent) {
        appOptions.render = h => h(opts.rootComponent);
      }
      if (!appOptions.data) {
        appOptions.data = {};
      }
      appOptions.data = { ...appOptions.data, ...props };
      instance.vueInstance = new opts.Vue(appOptions);
      if (instance.vueInstance.bind) {
        instance.vueInstance = instance.vueInstance.bind(instance.vueInstance);
      }
      mountedInstances[props.name] = instance;
    }else{
      instance.vueInstance.$el.style.display = "block";
    }
    return instance.vueInstance;
  });
}
function unmount(opts, mountedInstances, props) {
  return Promise.resolve().then(() => {
    const instance = mountedInstances[props.name];
    instance.vueInstance.$el.style.display = "none";
  });
}

Внутренние страницы подсистемы такие же, как и обычныеvueиспользовать ту же систему<keep-alive>теги для реализации кэширования.

Как реализовать предварительный запрос (предзагрузку) подсистем

vue-routerПри настройке роутинга можно использовать загрузку по требованию (код такой), после загрузки по требованию файл роутинга будет упакован в отдельныйjsа такжеcss.

path: "/about",
name: "about",
component: () => import( "../views/About.vue")

а такжеvue-cli3Сгенерированный шаблон упакованindex.htmlв использованииprefetchа такжеpreloadДля реализации предварительного запроса файла маршрутизации:

<link href=/js/about.js rel=prefetch>
<link href=/js/app.js rel=preload as=script>

prefetchПредварительный запрос: запрашивать и кэшировать файлы, когда сеть браузера простаивает.

systemJsМожно получить только входной файл, другие файлы маршрутизации загружаются по запросу, и предварительный запрос не может быть реализован. Но если вы не используете загрузку маршрутов по требованию, все файлы маршрутов упаковываются в один файл (app.js), предварительный запрос может быть реализован.

вышеизложенное завершеноdemoАдрес файла:GitHub.com/gongshun/четыре…

каркас цянькунь

qiankunОн основан на открытом исходном коде Ant Financial.single-spaВнешний микросервисный фреймворк для .

Как реализована js песочница (песочница)

Мы знаем все глобальные методы (alert,setTimeout,isNaNи т.д.), глобальные переменные/константы (NaN,Infinity,varобъявленные глобальные переменные и т. д.) и глобальные объекты (Array,String,Dateи др.) принадлежатwindowобъекта, что может привести кjsЗагрязняются именно эти глобальные методы и объекты.

такqiankunрешитьjsСпособ загрязнения: до загрузки подсистемыwindowОбъект делает снимок (копию), а затем восстанавливает снимок при выгрузке подсистемы, что гарантирует, что каждый раз при запуске подсистема будет совершенно новой.windowпредметная среда.

Итак, как контролироватьwindowКак насчет смены объекта, непосредственноwindowОчевидно, что невозможно выполнить глубокое копирование объекта, а затем детально сравнить каждый атрибут.qiankunФреймворк используетES6новые особенности,proxyметод прокси.

Конкретный код выглядит следующим образом (исходный кодtsверсия, я упростил и немного изменил):

// 沙箱期间新增的全局变量
const addedPropsMapInSandbox = new Map();
// 沙箱期间更新的全局变量
const modifiedPropsOriginalValueMapInSandbox = new Map();
// 持续记录更新的(新增和修改的)全局变量的 map,用于在任意时刻做 snapshot
const currentUpdatedPropsValueMap = new Map();
const boundValueSymbol = Symbol('bound value');
const rawWindow = window;
const fakeWindow = Object.create(null);
const sandbox = new Proxy(fakeWindow, {
    set(target, propKey, value) {
      if (!rawWindow.hasOwnProperty(propKey)) {
        addedPropsMapInSandbox.set(propKey, value);
      } else if (!modifiedPropsOriginalValueMapInSandbox.has(propKey)) {
        // 如果当前 window 对象存在该属性,且 record map 中未记录过,则记录该属性初始值
        const originalValue = rawWindow[propKey];
        modifiedPropsOriginalValueMapInSandbox.set(propKey, originalValue);
      }
      currentUpdatedPropsValueMap.set(propKey, value);
      // 必须重新设置 window 对象保证下次 get 时能拿到已更新的数据
      rawWindow[propKey] = value;
      // 在 strict-mode 下,Proxy 的 handler.set 返回 false 会抛出 TypeError,
      // 在沙箱卸载的情况下应该忽略错误
      return true;
    },
    get(target, propKey) {
      if (propKey === 'top' || propKey === 'window' || propKey === 'self') {
        return sandbox;
      }
      const value = rawWindow[propKey];
      // isConstructablev :监测函数是否是构造函数
      if (typeof value === 'function' && !isConstructable(value)) {
        if (value[boundValueSymbol]) {
          return value[boundValueSymbol];
        }
        const boundValue = value.bind(rawWindow);
        Object.keys(value).forEach(key => (boundValue[key] = value[key]));
        Object.defineProperty(value, boundValueSymbol, 
            { enumerable: false, value: boundValue }
        )
        return boundValue;
      }
      return value;
    },
    has(target, propKey) {
      return propKey in rawWindow;
    },
});

Общий принцип заключается в том, чтобы записыватьwindowСвойства и методы, которые добавляются, изменяются и удаляются для объекта во время работы подсистемы, а затем эти операции восстанавливаются при выгрузке подсистемы.

После этой обработки глобальные переменные могут быть восстановлены напрямую, но мониторинг событий и таймеры требуют специальной обработки: используйтеaddEventListenerДобавлены события, нужно использоватьremoveEventListenerметод удаления, таймер также нуждается в специальной функции для очистки. Таким образом, он переписывает привязку/отвязку событий и функции, связанные с таймером.

Переключить таймер (setInterval) часть кода выглядит следующим образом:

const rawWindowInterval = window.setInterval;
const hijack = function () {
  const timerIds = [];
  window.setInterval = (...args) => {
    const intervalId = rawWindowInterval(...args);
    intervalIds.push(intervalId);
    return intervalId;
  };
  return function free() {
    window.setInterval = rawWindowInterval;
    intervalIds.forEach(id => {
      window.clearInterval(id);
    });
  };
}

Мелкие детали: переключение подсистемы не может сразу сбросить таймер задержки подсистемы, например, подсистема имеетmessageПодскажите, автоматически закрывается через 3 секунды, если сразу очистить, то всегда будет существовать. Итак, насколько уместна задержка перед очисткой таймера подсистемы? 5 с? 7 с? 10 секунд? Ни то, ни другое не кажется идеальным, автор в конце концов решил не очищатьsetTimeout, ведь после однократного применения он бесполезен, да и эффекта мало.

Поскольку qiankun использует новую функцию прокси в функции песочницы js, его совместимость такая же, как у vue3, и он не поддерживает IE11 и ниже. Однако автор сказал, что вы можете попробовать отключить функцию песочницы для улучшения совместимости, но это не гарантирует работу. Уберите функцию песочницы js, она становится скучной.

Дополнение: обновление qiankun 2.x, для браузеров, не поддерживающих прокси, поддерживается метод diff для реализации песочницы, то есть мелкое копирование окна перед загрузкой подпроекта, а после выгрузки подпроекта мелкое копирование окна перед цикл for восстанавливает предыдущее состояние, но несколько подпроектов выполняются одновременно, они совместно используют песочницу, что эквивалентно отсутствию песочницы.

Дополнение: Как устранить влияние глобальных функций

functionключевое слово напрямую объявляет глобальную функцию, принадлежащуюwindowобъект, но не может бытьdelete:

function a(){}
Object.getOwnPropertyDescriptor(window, "a")
//控制台打印如下信息
/*{
    value: ƒ a(),
    writable: true,
    enumerable: true,
    configurable: false
}*/
delete window.a // 返回false,表示删除失败

configurable: true тогда и только тогда, когда описание свойства указанного объекта может быть изменено или свойство может быть удалено

Так как не может бытьdelete,ТакqiankunизjsКак это делает песочница и как она устраняет влияние глобальных функций подсистемы?

Есть два способа объявить глобальную функцию, один из нихfunctionКлючевое слово объявляется в глобальной среде, а другое добавляется в виде переменной:window.a = () => {}. мы знаемfunctionОбъявленная глобальная функция не может быть удалена, но переменная форма может быть удалена,qiankunпрямо избегалиfunctionглобальная функция, объявленная ключевым словом.

Сначала пишем в.vueфайл илиmain.jsв файлеfunctionНи одна из объявленных функций не является глобальной, они принадлежат только текущему модулю. Толькоindex.htmlГлобальная функция, написанная непосредственно в файле, или функция в распакованном файле является глобальной.

существуетindex.htmlГлобальная функция, написанная на , будет рассматриваться как локальная функция. Исходный код:

<script>
    function b(){}
    //测试全局变量污染
    console.log('window.b',window.b)
</script>

После обработки цянькунь:

(function(window){;
    function b(){}
    //测试全局变量污染
    console.log('window.b',window.b)
}).bind(window.proxy)(window.proxy);

Так как же он этого добился? Первое совпадение с регулярным выражением дляindex.htmlвнутренняя цепьjsи встроенныйjs, затем внешняя ссылкаjsПосле запроса строки содержимого сохраните ее в объекте, встроенномjsНепосредственное использование регулярного сопоставления с контентом также записывается в этот объект:

const fetchScript = scriptUrl => scriptCache[scriptUrl] ||
		(scriptCache[scriptUrl] = fetch(scriptUrl).then(response => response.text()));

Затем при запуске используйтеevalфункция:

//内联js
eval(`;(function(window){;${inlineScript}\n}).bind(window.proxy)(window.proxy);`)
//外链js
eval(`;(function(window){;${downloadedScriptText}\n}).bind(window.proxy)(window.proxy);`))

При этом он будет учитывать и внешнюю цепочкуjsизasyncсвойств, то есть с учетомjsПорядок выполнения файлов, надо сказать, у этого автора действительно изобилует деталями.

Как решить загрязнение css

это решаетcssСпособ загрязнения заключается во введении подсистемы, когда подсистема разгружена.cssв использовании<link>,<style>Этикетка удалена. Способ удаления - переписать<head>помеченappendChildМетод аналогичен перезаписи таймера.

Когда подсистема загружается, требуетсяjs/cssфайл вставляется в<head>теги, при переопределенииappendChildМетод записывает вставленные теги, а затем удаляет эти теги при выгрузке подсистемы.

Как реализуются предварительные запросы

Основой решения предварительного запроса подсистемы является то, что нам нужно знать, какие подсистемы имеютjs/cssнеобходимо загрузить, и с помощьюsystemJsЗагрузите подсистему, зная только входной файл подсистемы (app.js).qiankunне только поддерживаетapp.jsВ качестве входного файла он также поддерживаетindex.htmlВ качестве входного файла он будет использовать обычное сопоставление для выводаindex.htmlвнутриjs/cssтеги, а затем реализовать предварительные запросы.

Когда сеть плохая и доступ к мобильному терминалу есть,qiankunПредварительный запрос не выполняется. Большинство мобильных терминалов используют трафик данных, а предварительный запрос приводит к потере пользовательского трафика. Код решения выглядит следующим образом:

const isMobile = 
   /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const isSlowNetwork = navigator.connection
  ? navigator.connection.saveData || /(2|3)g/.test(navigator.connection.effectiveType)
  : false;

проситьjs/cssфайл, который он используетfetchзапрос, или если браузер его не поддерживаетpolyfill.

Следующий код - это то, что он запрашиваетjsи кэш:

const defaultFetch = window.fetch.bind(window);
//scripts是用正则匹配到的script标签
function getExternalScripts(scripts, fetch = defaultFetch) {
    return Promise.all(scripts.map(script => {
	if (script.startsWith('<')) {
	    // 内联js代码块
	    return getInlineCode(script);
	} else {
	    // 外链js
	    return scriptCache[script] ||
	           (scriptCache[script] = fetch(script).then(response => response.text()));
	}
    }));
}

Внедрение микроинтерфейса с помощью фреймворка qiankun

qiankunизисходный кодПример использования приведен в разделе , и он очень прост и удобен в использовании. Далее я покажу, как использовать его с 0qianklunФреймворк реализует микро-интерфейс, а контент адаптирован из официального примера использования. PS: Следующее основано наqiankun1Версия, версия 2.x см.:Преобразование существующего проекта в подпроект qiankunа такжедемо на гитхабе

основной проект

  1. vue-cli3создать совершенно новыйvueProject, обратите внимание на использование маршрутизацииhistoryмодель.
  2. УстановитьqiankunРамка:npm i qiankun -S
  3. Исправлятьapp.vue, что делает его контейнером для меню и подпунктов. Два данных,loadingэто загруженное состояние, иcontentгенерируется подсистемойHTMLФрагменты (когда подсистемы работают независимо, этоHTMLФрагмент будет вставлен в#appвнутри)
<template>
  <div id="app">
    <header>
      <router-link to="/app-vue-hash/">app-vue-hash</router-link>
      <router-link to="/app-vue-history/">app-vue-history</router-link>
    </header>
    <div v-if="loading" class="loading">loading</div>
    <div class="appContainer" v-html="content">content</div>
  </div>
</template>

<script>
export default {
  props: {
    loading: {
      type: Boolean,
      default: false
    },
    content: {
      type: String,
      default: ''
    },
  },
}
</script>
  1. Исправлятьmain.js, зарегистрируйте подпроект, файл записи подпроекта принимаетindex.html
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { registerMicroApps, start } from 'qiankun';
Vue.config.productionTip = false
let app = null;
function render({ appContent, loading }) {
  if (!app) {
    app = new Vue({
      el: '#container',
      router,
      data() {
        return {
          content: appContent,
          loading,
        };
      },
      render(h){
        return h(App, {
          props: {
            content: this.content,
            loading: this.loading,
          },
        })
      } 
    });
  } else {
    app.content = appContent;
    app.loading = loading;
  }
}
function initApp() {
  render({ appContent: '', loading: false });
}
initApp();
function genActiveRule(routerPrefix) {
  return location => location.pathname.startsWith(routerPrefix);
}
registerMicroApps([
  { 
    name: 'app-vue-hash',
    entry: 'http://localhost:80', 
    render, 
    activeRule: genActiveRule('/app-vue-hash')
  },
  { 
    name: 'app-vue-history', 
    entry: 'http://localhost:1314',
    render, 
    activeRule: genActiveRule('/app-vue-history') 
  },
]);
start();

Примечание: в основном проектеindex.htmlв шаблоне<div id="app"></div>необходимо изменить на<div id="container"></div>

Подпроект app-vue-hash

  1. vue-cli3создать совершенно новыйvueProject, обратите внимание на использование маршрутизацииhashмодель.
  2. существуетsrcНовый файл каталогаpublic-path.js, в основном используется для изменения подпроектаpublicPath.
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  1. Исправлятьmain.js, экспорт вместе с основным проектомsingle-spaТребуются три жизненных цикла. Примечание. Создание экземпляра маршрута должно быть завершено в main.js, чтобы облегчить уничтожение маршрутов, поэтому файл маршрута должен только экспортировать конфигурацию маршрута (исходный шаблон экспортирует экземпляр маршрута).
import './public-path';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';

Vue.config.productionTip = false;
let router = null;
let instance = null;
function render() {
  router = new VueRouter({
    routes,
  });
  instance = new Vue({
    router,
    store,
    render: h => h(App),
  }).$mount('#appVueHash');// index.html 里面的 id 需要改成 appVueHash,否则子项目无法独立运行
}
if (!window.__POWERED_BY_QIANKUN__) {//全局变量来判断环境
  render();
}
export async function bootstrap() {
  console.log('vue app bootstraped');
}
export async function mount(props) {
  console.log('props from main framework', props);
  render();
}
export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}
  1. Изменить файл конфигурации упаковкиvue.config.js, в основном для обеспечения междоменного доступа и упаковки вumdФормат
const { name } = require('./package');

const port = 7101; // dev port
module.exports = {
  devServer: {
    disableHostCheck: true,
    port,
    overlay: {
      warnings: false,
      errors: true,
    },
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  // 自定义webpack配置
  configureWebpack: {
    output: {
      // 把子应用打包成 umd 库格式
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

Подпроект app-vue-история

historyмодальныйvueпроект сhashВ одном месте схема другая, в остальном все точно так же.

которыйmain.jsПри создании экземпляра маршрута вам необходимо добавить условное суждение и ввести префикс маршрута.

function render() {
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/app-vue-history' : '/',
    mode: 'history',
    routes,
  });

  instance = new Vue({
    router,
    store,
    render: h => h(App),
  }).$mount('#appVueHistory');
}

общение между проектами

Пользовательские события могут передавать данные, но это не кажется идеальным, данные не являются «двусторонними». Если мы хотим изменить эти данные как в родительском, так и в дочернем проектах и ​​ответить, нам нужно реализовать высокоуровневыйvuex.

Конкретные идеи:

  1. В основном проекте создайте экземплярVuex, а затем передается в подпроект при регистрации подпроекта
  2. подпроект вmountedЖизненный цикл получает основной проектVuex, а затем зарегистрируйтесь в глобальном go:new Vueкогда вdataВ декларации, чтобы любой компонент подпроекта мог пройтиthis.$rootпосетите этоVuex.

Примерный код выглядит следующим образом:

основной проектmain.js:

import store from './store';

registerMicroApps([
  { 
    name: 'app-vue-hash', 
    entry: 'http://localhost:7101', 
    render, 
    activeRule: genActiveRule('/app-vue-hash'), 
    props: { data : store } 
  },
  { 
    name: 'app-vue-history', 
    entry: 'http://localhost:1314', 
    render, 
    activeRule: genActiveRule('/app-vue-history'), 
    props: { data : store } 
  },
]);

подпроектmain.js:

function render(parentStore) {
  router = new VueRouter({
    routes,
  });
  instance = new Vue({
    router,
    store,
    data(){
      return {
        store: parentStore,
      }
    },
    render: h => h(App),
  }).$mount('#appVueHash');
}
export async function mount(props) {
  render(props.data);
}

подпроектHome.vueиспользуется в:

<template>
  <div class="home">
    <span @click="changeParentState">主项目的数据:{{ commonData.parent }},点击变为2</span>
  </div>
</template>
<script>
export default {
  computed: {
    commonData(){
      return this.$root.store.state.commonData;
    }
  },
  methods: {
    changeParentState(){
      this.$root.store.commit('setCommonData', { parent: 2 });
    }
  },
}
</script>

разное

  1. Если вы хотите закрытьjsПесочница и предварительный запрос, вstartЕго можно настроить в функции
start({
    prefetch: false, //默认是true,可选'all'
    jsSandbox: false, //默认是true
})
  1. Функция регистрации подпроектаregisterMicroAppsВы также можете передавать данные в подпроекты и устанавливать глобальные функции жизненного цикла.
// 其中app对象的props属性就是传递给子项目的数据,默认是空对象
registerMicroApps(
  [
    { 
        name: 'app-vue-hash', 
        entry: 'http://localhost:80', 
        render, activeRule: 
        genActiveRule('/app-vue-hash') , 
        props: { data : 'message' } 
    },
    { 
        name: 'app-vue-history', 
        entry: 'http://localhost:1314', 
        render, 
        activeRule: genActiveRule('/app-vue-history') 
    },
  ],
  {
    beforeLoad: [
      app => { console.log('before load', app); },
    ],
    beforeMount: [
      app => { console.log('before mount', app); },
    ],
    afterUnmount: [
      app => { console.log('after unload', app); },
    ],
  },
);
  1. qiankunОфициальная документация для:Поклонники Qiankun.U accept.org/this/API/#reg…

  2. вышесказанноеdemoполный кодGitHub.com/gongshun/its…

Суммировать

  1. jsПесочница не решает всегоjsзагрязнение, например, я даю<body>Добавлено событие клика,jsПесочница не может устранить его влияние, поэтому оно зависит от спецификации кода и самосознания.

  2. Совместимость в стороне, я думаюqiankunОн действительно прост в использовании, не нужно вносить слишком много изменений в подпроекты, он работает из коробки. Также нет необходимости выполнять какие-либо дополнительные работы по разработке и развертыванию подпроектов.

  3. qiankunиспользование кадраindex.htmlКак вход подпроекта, внутриstyle/link/scriptТеги и код комментария разбираются и вставляются, но он не считаетmetaа такжеtitleэтикетка, если коммутационные системы, гдеmetaЕсли тег изменится, то он, конечно же, не будет парситься и вставляться.metaТеги не влияют на отображение страницы, таких сценариев немного. И переключите систему, измените страницуtitle, это должно быть реализовано через глобальную функцию ловушки.

  4. qiankunФреймворк не прост в реализацииkeep-aliveпотребности, потому что решениеcss/jsСпособ загрязнения - удалить теги, вставленные подсистемой, и угнатьwindowобъект, при выгрузке восстанавливается до того, что было до загрузки подсистемы, что аналогичноkeep-aliveПротиворечие:keep-aliveПросьба сохранить их — это просто стилистическое сокрытие.

  5. Существует два распространенных способа входа в файл входа подпроекта в микро-интерфейсе:JS entryа такжеHTML entry

чистыйsingle-spaиспользуетсяJS entry,а такжеqiankunоба поддерживаютJS entryи поддерживаетHTML entry.

JS entryТребования более жесткие:

(1) будетcssупакован вjs в

(2) удалитьchunk-vendors.js,

(3) Удалить имя файлаhashстоимость

(4) Измените файл ввода (app.js) помещается вindex.htmlкаталог, остальные файлы остаются без изменений, причина в перехватеapp.jsпуть какpublicPath

APP entry преимущество недостаток
JS entry Может повторно использовать общедоступные зависимости (vue, vuex, vue-router и т. д.) Требуются различные конфигурации упаковки, и предварительная загрузка не может быть достигнута
HTML entry Просто и удобно, можно предварительно загрузить Для еще одного уровня запросов вам нужно сначала запросить файл HTML, а затем использовать обычное сопоставление для сопоставления js и css в нем, и вы не можете повторно использовать общедоступные зависимости (vue, vuex, vue-router и т. д.)

Я думаю, что файл записи можно изменить на комбинацию двух, используя объект для настройки:

{
  publicPath: 'http://www.baidu.com',
  entry: [
    "app.3249afbe.js"
    "chunk-vendors.75fba470.js",
  ],
  preload: [
    "about.3149afve.js",
    "test.71fba472.js",
  ]
}

Таким образом, может быть достигнута предварительная загрузка и повторное использование общих зависимостей без изменения слишком большого количества конфигураций пакетов. Сложность заключается в том, как совместить необходимыеjsЕсть две идеи для записи файла в файл конфигурации: Способ 1: ЗапишитеnodeService, периодически (или при обновлении подсистемы) для запросаindex.htmlфайл, а затем регулярное выражение соответствует внутренней частиjs. Способ 2: Когда подсистема упакована,webpackбудет генерироватьjs/cssзапрос файла для вставки вindex.htmlсередина(HtmlWebpackPlugin), то можно ли также преобразовать этиjsИмя файла отправляется на сервер для регистрации, но несколько статичноjsЕсли файл не упакован, его необходимо настроить вручную.

Наконец, если у вас есть какие-либо вопросы или ошибки, пожалуйста, укажите и растите друг друга, спасибо!