Vite micro front-end практика, внедрение компонентного решения

внешний интерфейс JavaScript
Vite micro front-end практика, внедрение компонентного решения

Что такое микрофронтенд

Микрофронтенд — это технические средства и стратегия методов, позволяющая нескольким командам совместно создавать современные веб-приложения путем независимой публикации функций.

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

характеристика

  • Независимость от стека технологийОсновная структура не ограничивает технологический стек приложения доступа, а подприложение может самостоятельно выбирать технологический стек.
  • Самостоятельная разработка/развертываниеСклады между каждой командой независимы, развертываются отдельно и не зависят друг от друга
  • Инкрементное обновлениеКогда приложение большое, обновление или рефакторинг технологии довольно проблематичны, в то время как микроприложения имеют характеристики постепенного обновления.
  • При самостоятельном бегеСреды выполнения микроприложений не зависят друг от друга и имеют независимое управление состоянием.
  • Повысить эффективностьЧем больше приложение, тем сложнее его поддерживать и тем менее эффективна совместная работа. Микроприложения могут быть хорошо разделены для повышения эффективности

В настоящее время доступны решения для микроинтерфейса

В настоящее время существуют следующие типы микро-интерфейсных решений:

на основеiframeПолностью изолированное решение

Как фронтенд-разработчики, мыiframeУже очень знакомо, одно приложение может запускать другое приложение независимо. Он имеет существенные преимущества:

  1. Очень просто без каких-либо изменений
  2. Идеальная изоляция, JS и CSS — независимые операционные среды.
  3. Неограниченное использование, на странице можно разместить несколько страницiframeсовмещать бизнес

Конечно, недостатки тоже очень заметны:

  1. Состояние маршрутизации не может быть сохранено, и состояние маршрутизации теряется после обновления.
  2. Полная изоляция делает взаимодействие с подприложениями чрезвычайно сложным.
  3. iframeВсплывающее окно не может пробить себя
  4. Все приложение загружается с полными ресурсами, и загрузка слишком медленная

Эти существенные недостатки также привели к созданию других решений.

на основеsingle-spaсхема захвата маршрута

single-spaПереключение между подприложениями осуществляется путем перехвата маршрута, но метод доступа должен интегрировать собственный маршрут, что имеет определенные ограничения.

qiankunРазработано на базе унифицированной платформы доступа Ant Financial Technology для облачных продуктов на основе микроинтерфейсной архитектуры. это правильноsingle-spaСделайте слой упаковки. В основном решеноsingle-spaнекоторые болевые точки и недостатки. пройти черезimport-html-entryразбор пакетаHTMLПолучите путь к ресурсу, а затем проанализируйте и загрузите ресурс.

Изменяя среду выполнения, он достигает JS 沙箱,样式隔离и другие характеристики.

Цзиндонmicro-appстроить планы

Цзиндонmicro-appне следилsingle-spaидеи, но заимствованные изWebComponentмысль, черезCustomElementв сочетании с обычайShadowDom, который инкапсулирует микроинтерфейс в классwebComponentsкомпонентов, чтобы реализовать компонентный рендеринг микроинтерфейсов.

существуетViteиспользование микрофронтендов на

мы начинаем с我们从 UmiJS 迁移到了 ViteПосле этого микро-фронтенд также стал императивом, и в то время было исследовано множество решений.

почему это не работаетqiankun

qiankunВ настоящее время это основное микроинтерфейсное решение в сообществе. Хотя он совершенен и популярен, самая большая проблема в том, что он не поддерживаетVite. это основано наimport-html-entryРазобрать HTML для получения ресурсов, так какqiankunчерезevalвыполнить этиjsсодержание, при этомViteсерединаscriptТип этикеткиtype="module", Который содержитimport/exportи другой код модуля, поэтому он сообщит об ошибке: не разрешено в не-type="module"изscriptвнутри использованиеimport.

Сделав шаг назад к реализации, мы принялиsingle-spaспособ и использованиеsystemjsВ пути реализована схема микролобовой загрузки, да еще и на ямы наступило немало.single-spaНет дружественного учебника для доступа.Хотя есть много документов, большинство из них говорят о концепциях.В то время люди чувствовали, что это было эзотерическое чувство.

Позже я посмотрел его исходный код и обнаружил, что это то, что есть... Большая часть кода в нем разработана вокруг перехвата маршрутизации, и в документации нет ощущения величия. И мы не можем использовать его функцию перехвата маршрутизации, так зачем мы ее используем?

На уровне компонентаsingle-spaЭтот подход совсем не элегантен.

  1. перехватывает маршрутизацию сreact-routerНесовместимо с компонентным мышлением
  2. Множество сложных конфигураций для методов доступа
  3. Одиночный экземпляр программы, а именно то же самое время, показано только субприложение

Подумав оsingle-spaМы можем реализовать компонентное микро-интерфейсное решение самостоятельно.

Как реализовать простое, прозрачное и компонентное решение

С помощью компонентного мышления реализовать микроприложение очень просто:Подприложение экспортирует метод, а основное приложение загружает подприложение и вызывает метод, передаваяElementПараметры узла, подприложение получаетElementузел, который будет его собственным компонентомappendChildприбытьElementна узле.

micro-app-2.png

соглашение о типах

Перед этим нам нужно согласовать способ взаимодействия между основным приложением и под-приложением. В основном с помощью трех хуков для обеспечения правильного выполнения, обновления и удаления приложения.

Определение типа:

export interface AppConfig {
  // 挂载
  mount?: (props: unknown) => void;
  // 更新
  render?: (props: unknown) => ReactNode | void;
  // 卸载
  unmount?: () => void;
}

Экспорт вспомогательного приложения

С соглашениями о типах мы можем экспортировать подприложения:mount,render,unmountдля основного крючка.

ReactРеализация подприложения:

export default (container: HTMLElement) => {
  let handleRender: (props: AppProps) => void;

  // 包裹一个新的组件,用作更新处理
  function Main(props: AppProps) {
    const [state, setState] = React.useState(props);
    // 将 setState 方法提取给 render 函数调用,保持父子应用触发更新
    handleRender = setState;
    return <App {...state} />;
  }

  return {
    mount(props: AppProps) {
      ReactDOM.render(<Main {...props} />, container);
    },
    render(props: AppProps) {
      handleRender?.(props);
    },
    unmount() {
      ReactDOM.unmountComponentAtNode(container);
    },
  };
};

Реализация подприложения Vue:

import { createApp } from 'vue';
import App from './App.vue';

export default (container: HTMLElement) => {
  // 创建
  const app = createApp(App);
  return {
    mount() {
      // 装载
      app.mount(container);
    },
    unmount() {
      // 卸载
      app.unmount();
    },
  };
};

реализация основного приложения

Reactвыполнить

Его основной код составляет всего более десяти строк, в основном связанных с взаимодействием с подприложениями.(код обработки ошибок скрыт для удобства чтения):

export function MicroApp({ entry, ...props }: MicroAppProps) {
  // 传递给子应用的节点
  const containerRef = useRef<HTMLDivElement>(null);
  // 子应用配置
  const configRef = useRef<AppConfig>();

  useLayoutEffect(() => {
    import(/* @vite-ignore */ entry).then((res) => {
      // 将 div 传给子应用渲染
      const config = res.default(containerRef.current);
      // 调用子应用的装载方法
      config.mount?.(props);
      configRef.current = config;
    });
    return () => {
      // 调用子应用的卸载方法
      configRef.current?.unmount?.();
      configRef.current = undefined;
    };
  }, [entry]);

  return <div ref={containerRef}>{configRef.current?.render?.(props)}</div>;
}

Готово, теперь реализованы операции загрузки, обновления и выгрузки основного приложения и подприложений. Теперь это компонент, который может отображать несколько различных подприложений одновременно, что болееsingle-spaМного элегантности.

входной адрес суб-приложения, конечно, реальная ситуация будет основываться наdevа такжеprodРежимы дают разные адреса:

<MicroApp className="micro-app" entry="//localhost:3002/src/main.tsx" />

Vueвыполнить

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';

const { entry, ...props } = defineProps<{ entry: string }>();
const container = ref<HTMLDivElement | null>(null);
const config = ref();

onMounted(() => {
  const element = container.value;
  import(/* @vite-ignore */ entry).then((res) => {
    // 将 div 传给子应用渲染
    const config = res.default(element);
    // 调用子应用的装载方法
    config.mount?.(props);
    config.value = config;
  });
});

onUnmounted(() => {
  // 调用子应用的卸载方法
  config.value?.unmount?.();
});
</script>

<template>
  <div ref="container"></div>
</template>

micro-app-demo.gif

Как заставить вспомогательные приложения работать независимо

single-spaи многие другие решения, все монтируют переменную вwindowВыше, судя по тому, находится ли переменная в среде микроинтерфейса, это очень неэлегантно. существуетESM, мы можем пройтиimport.meta.urlВходящие параметры для оценки:

if (!import.meta.url.includes('microAppEnv')) {
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root'),
  );
}

Модификация импорта импорта:

// 添加环境参数和当前时间避免被缓存
import(/* @vite-ignore */ `${entry}?microAppEnv&t=${Date.now()}`);

Совместимость с браузером

IEБраузеры постепенно уходят из нашего поля зрения, основываясь наVite, нам просто нужно поддержатьimportБраузера функций достаточно. Конечно, если учестьIEЭто не невозможно для браузера, это очень просто: поместите приведенный выше кодimportзаменитьSystem.importкоторыйsystemjs,Слишкомsingle-spaрекомендуемое использование.

браузер Chrome Edge Firefox Internet Explorer Safari
import 61 16 60 No 10.1
Dynamic import 63 79 67 No 11.1
import.meta 64 79 62 No 11.1

общий модуль

Наши дочерние компоненты должны использоватьmount,unountРежим? Ответ не обязательно, если наш стек технологийReactесли. Наше подприложение экспортирует только одинrenderдостаточно. Это тоже самоеReactПреимущество рендеринга в том, что дочернее приложение может потреблять родительское приложение.Provider. Но есть предпосылка, что между двумя приложениямиReactДолжен быть один и тот же экземпляр, иначе будет сообщено об ошибке.

мы можем поставитьreact,react-dom,styled-componetsи другие часто используемые модули заранее упакованы вESMмодуль, а затем поместите его в файловую службу для использования.

ИзменятьViteконфиг добавитьalias:

defineConfig({
  resolve: {
    alias: {
      react: '//localhost:8000/react@17.js',
      'react-dom': '//localhost:8000/react-dom@17.js',
    },
  },
});

Таким образом, вы можете с удовольствием использовать тот жеReactкод вверх. Он также может извлекать общие модули между основным приложением и подприложениями, уменьшая общий объем приложения. Конечно если нетhttp2Если это так, вам необходимо рассмотреть вопрос детализации.

онлайнCDNстроить планы:esm.sh

Еще одинimportmapСхема, совместимость не очень, но за трендом будущее:

<script type="importmap">
  {
    "imports": {
      "react": "//localhost:8000/react@17.js"
    }
  }
</script>

общение отца и сына

Компонентные микроприложения, которые могут передавать параметры и обмениваться данными, полностьюReactМодель взаимодействия компонентов.

путь к ресурсу

существуетViteизdevВ режиме статические ресурсы в подприложении обычно представлены следующим образом:

import logo from './images/logo.svg';

<img src={logo} />;

Путь к изображению:/basename/src/logo.svg, он получит 404 при отображении в основном приложении. Потому что путь существует только в подприложении. нам нужно сотрудничатьURLИспользование модуля, так что путь будет предшествоватьoriginПриставка:

const logoURL = new URL(logo, import.meta.url);

<img src={logoURL.href} />;

Конечно, использовать его таким образом громоздко, мы можем инкапсулировать его какViteПлагин обрабатывает эту сцену автоматически.

синхронизация маршрута

Использование проектаreact-router, то у него может быть проблема рассинхронизации маршрутизации, потому что это не то же самоеreact-routerпример. То есть между маршрутами нет связи.

существуетreact-routerПоддержка настройкиhistoryбиблиотеку, мы можем создать:

import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();

// 主应用:路由入口
<HistoryRouter history={history}>{children}</HistoryRouter>;

// 主应用:传递给子应用
<Route
  path="/child-app/*"
  element={<MicroApp entry="//localhost:3002/src/main.tsx" history={history} />}
/>;

// 子应用:路由入口
<HistoryRouter basename="/child-app" history={history}>
  {children}
</HistoryRouter>;

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

Примечание: вспомогательное приложениеbasenameдолжен быть связан с основным приложениемpathИмя остается прежним. Здесь нужно изменитьViteКонфигурацияbaseПоле:

export default defineConfig({
  base: '/child-app/',
  server: {
    port: 3002,
  },
  plugins: [react()],
});

JS-песочница

потому что песочницаESMНе поддерживается, так как модуль в среде выполнения не может быть динамически изменен.windowобъект, а также не может быть введен новый глобальный объект.

в общемReact,VueКроме того, в проекте редко изменяются глобальные переменные, и очень важно хорошо выполнить проверку спецификации кода.

Изоляция стилей CSS

автоматическийCSSИзоляция стилей обходится дорого, обычно мы рекомендуем, чтобы подприложения использовали разныеCSSпрефикс, затем соответствиеCSS ModulesВ принципе соответствует требованиям.

Развертывание пакета

Конфигурация Sub-App Vite

export default defineConfig({
  base: '/basename/',
  server: {
    port: 3002,
  },
  build: {
    rollupOptions: {
      // 用于控制 Rollup 尝试确保入口块与基础入口模块具有相同的导出
      preserveEntrySignatures: 'allow-extension',
      input: 'src/main.tsx',
      output: {
        entryFileNames: '[name].js',
      },
    },
  },
  plugins: [react()],
});

Развертывание может быть основано наbaseПоместите их в разные каталоги и сопоставьте имена. НастроеноnginxПравила переадресации в порядке. Мы можем унифицировать префикс маршрутизации подприложений, чтобы облегчитьnginxРазделите основные приложения и настройте общие правила.

Например, размещение основного приложения вsystemкаталог, подприложения помещаются вapp-Каталог, который начинается с:

location ~ ^/app-.*(..+)$ {
    root /usr/share/nginx/html;
}

location / {
    try_files $uri $uri/ /index.html;
    root /usr/share/nginx/html/system;
    index  index.html index.htm;
}

преимущество

  • ПростойЯдро состоит менее чем из 100 строк кода, дополнительная документация не требуется.
  • гибкийПо соглашению о доступе это может быть прогрессивное улучшение
  • ПрозрачныйНет схемы угона, больше логической прозрачности
  • составнойКомпонентный рендеринг и передача параметров
  • На основе ЕСМГотовность к будущему благодаря поддержке Vite
  • обратная совместимостьДополнительное решение SystemJS, совместимое с браузерами более ранних версий.

есть пример

Пример кода находится наGithub, заинтересованные друзья могутcloneСпускайся и учись.Поскольку наш стек технологийReact, поэтому реализация основного приложения этого примера используетReact.

Эпилог

Решение с микро-интерфейсом лучше всего подходит для командного сценария, и особенно важно создать решение, которым может управлять команда.

Использованная литература:

  1. developer.Mozilla.org/this-cn/docs/…
  2. developer.Mozilla.org/this-cn/docs/…

Практика миграции Vite

Мы перешли с UmiJS на Vite

нажмитеДобавить Реагирующую группуобщаться с. Добро пожаловать на общедоступный номер:前端星辰