Практика микроинтерфейса приложения CMS на основе qiankun

CMS React.js
Практика микроинтерфейса приложения CMS на основе qiankun

Источник изображения:zhuanlan.zhihu.com/p/78362028

Автор этой статьи: Ши Чжипэн

предисловие

Фоновый проект прямой трансляции LOOK — это «приложение для валунов», которое повторялось более 2 лет, в развитии бизнеса участвовало более 10+ разработчиков и содержалось более 250 страниц. Огромный объем кода приводит к неэффективному построению и развертыванию, кроме того, проект опирается на внутренний наборRegularjsСтек технологий также выполнил свою историческую миссию, и соответствующую библиотеку компонентов пользовательского интерфейса и инженерные леса также было рекомендовано прекратить использовать, переходя к стадии меньшего обслуживания или отсутствия обслуживания. Поэтому в график работы был включен фон операции прямой трансляции LOOK на основе нового строительства React и разделения проекта. Цель состоит в том, чтобы описать цель одним предложением: новая страница будет разработана в новом проекте на основе React, проект React может быть развернут независимо, и ожидается, что адрес доступа, выдаваемый фоном операции прямой трансляции LOOK, останется неизменным. .

Эта статья основана на практике посадки микро-интерфейса на фоне операции прямой трансляции LOOK. В этой статье в основном представлен процесс посадки микроинтерфейса приложений CMS с использованием микроинтерфейса фреймворка qiankun в сценарии, в котором сосуществуют существующее «приложение Boulder», стеки технологий Regularjs и React.

Для ознакомления с qiankun перейдите по ссылкеофициальныйПроверьте это, эта статья не будет посвящена представлению концепций микрофронтендов.

1. Предпосылки

1.1 Статус

  1. Как упоминалось выше, есть приложение CMS, как показано на рисунке ниже, проект этого приложения называется liveadmin, а адрес доступа:https://example.com/liveadmin, доступ, как показано ниже.

  1. Мы надеемся, что больше не будем добавлять новые бизнес-страницы в старый проект liveadmin, поэтому мы создали новый проект под названием «Увеличение» на основе внутренних шаблонов React. Этот проект рекомендуется для новых бизнес-страниц. Это приложение можно развернуть независимо и получить к нему доступ независимо адрес доступа:https://example.com/lookadmin, доступ, как показано ниже:

1.2 Цели

Мы надеемся использовать метод микроинтерфейса для интеграции всех меню двух приложений, чтобы пользователь не воспринимал это изменение и по-прежнему следовал исходному методу доступа.https://example.com/liveadmin, вы можете получить доступ ко всем страницам liveadmin и увеличить проекты.
Для такой цели нам необходимо решить следующие две основные проблемы:

  1. Комбинированное отображение меню двух систем;
  2. Используйте исходный адрес доступа для доступа к страницам двух приложений.

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

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

1.3 Управление правами

Говоря о CMS, необходимо также рассказать о реализации системы управления правами, именуемой в дальнейшем PMS.

  1. Разрешения: в настоящее время в нашей PMS определены два типа разрешений: разрешения страницы (определяющие, может ли пользователь просматривать определенную страницу) и разрешения функции (определяющие, может ли пользователь получить доступ к API функции). Внешний интерфейс отвечает за реализацию разрешений для страниц, а разрешения функций управляются и контролируются сервером.
  2. Управление разрешениями: в этой статье описывается только управление разрешениями страниц. Во-первых, каждое внешнее приложение связано с приложением разрешений PMS, например, liveadmin связано с приложением разрешений appCode = live_backend. После успешного развертывания проекта внешнего интерфейса отправьте страницу внешнего проекта и данные кода разрешения, связанные со страницей, в PMS через черный ход. Операция управления рисками находит соответствующее приложение разрешений в системе PMS, назначает разрешения страниц в соответствии с гранулярностью роли, и пользователи с ролью могут получить доступ к страницам, назначенным ролью.
  3. Управление разрешениями: при доступе к внешнему приложению самый внешний модуль отвечает за запрос списка кодов разрешений страницы текущего пользователя, а затем фильтрует действительные меню, к которым можно получить доступ в соответствии со списком кодов разрешений, регистрируя маршруты действительные меню и, наконец, создание текущей страницы.Легитимное приложение меню с разрешениями пользователя.

2. Реализация

2.1 основное приложение lookcms

  1. Прежде всего, создайте новый базовый проект CMS, определите его как основные внешности приложения, и имеют основные функции запроса разрешений, данных меню и меню рендеринга.

Файл входа выполняет следующие функции запроса разрешений и данных меню, а также рендеринга меню.

// 使用 Redux Store 处理数据
const store = createAppStore(); 
// 检查登录状态
store.dispatch(checkLogin());
// 监听异步登录状态数据
const unlistener = store.subscribe(() => {
    unlistener();
    const { auth: { account: { login, name: userName } } } = store.getState();
    if (login) { // 如果已登录,根据当前用户信息请求当前用户的权限和菜单数据
        store.dispatch(getAllMenusAndPrivileges({ userName }));
        subScribeMenusAndPrivileges();
    } else {
        injectView(); // 未登录则渲染登录页面
    }
});
// 监听异步权限和菜单数据
const subScribeMenusAndPrivileges = () => {
    const unlistener = store.subscribe(() => {
        unlistener();
        const { auth: { privileges, menus, allMenus, account } } = store.getState();
        store.dispatch(setMenus(menus)); // 设置主应用的菜单,据此渲染主应用 lookcms 的菜单
        injectView(); // 挂载登录态的视图
        // 启动qiankun,并将菜单、权限、用户信息等传递,用于后续传递给子应用,拦截子应用的请求
        startQiankun(allMenus, privileges, account, store); 
    });
};
// 根据登录状态渲染页面
const injectView = () => {
    const { auth: { account: { login } } } = store.getState();
    if (login) {
        new App().$inject('#j-main');
    } else {
        new Auth().$inject('#j-main');
        window.history.pushState({}, '', `${$config.rootPath}/auth?redirect=${window.location.pathname}`);
    }
};

  1. Введите qiankun и зарегистрируйте два дополнительных приложения liveadmin и увеличьте.

Определите подприложение и определите поля name, entry, container и activeRule в соответствии с официальными документами qiankun.Конфигурация входа обращает внимание на различение среды и получает меню, привилегии и другие данные предыдущего шага. основной код выглядит следующим образом:

// 定义子应用集合
const subApps = [{ // liveadmin 旧工程
    name: 'music-live-admin', // 取子应用的 package.json 的 name 字段
    entrys: { // entry 区分环境
        dev: '//localhost:3001',
        // liveadmin这里定义 rootPath为 liveadminlegacy,便于将原有的 liveadmin 释放给主应用使用,以达到使用原始访问地址访问页面的目的。
        test: `//${window.location.host}/liveadminlegacy/`,
        online: `//${window.location.host}/liveadminlegacy/`,
    },
    pmsAppCode: 'live_legacy_backend', // 权限处理相关
    pmsCodePrefix: 'module_livelegacyadmin', // 权限处理相关
    defaultMenus: ['welcome', 'activity']
}, { // increase 新工程
    name: 'music-live-admin-react',
    entrys: {
        dev: '//localhost:4444',
        test: `//${window.location.host}/lookadmin/`,
        online: `//${window.location.host}/lookadmin/`,
    },
    pmsAppCode: 'look_backend',
    pmsCodePrefix: 'module_lookadmin',
    defaultMenus: []
}];
// 注册子应用
registerMicroApps(subApps.map(app => ({
    name: app.name,
    entry: app.entrys[$config.env], // 子应用的访问入口
    container: '#j-subapp', // 子应用在主应用的挂载点
    activeRule: ({ pathname }) => { // 定义加载当前子应用的路由匹配策略,此处是根据 pathname 和当前子应用的菜单 key 比较来做的判断
        const curAppMenus = allMenus.find(m => m.appCode === app.pmsAppCode).subMenus.map(({ name }) => name);
        const isInCurApp = !!app.defaultMenus.concat(curAppMenus).find(headKey => pathname.indexOf(`${$config.rootPath}/${headKey}`) > -1);
        return isInCurApp;
    },
    // 传递给子应用的数据:菜单、权限、账户,可以使得子应用不再请求相关数据,当然子应用需要做好判断
    props: { menus: allMenus.find(m => m.appCode === app.pmsAppCode).subMenus, privileges, account }
})));
// ...
start({ prefetch: false });
  1. Логика главного меню приложения

На основе существующих данных меню мы используем внутренние компоненты пользовательского интерфейса для завершения рендеринга меню, привязки события щелчка к каждому меню и изменения пути окна через pushState после нажатия. Например, щелкните меню a-b, соответствующий маршрутhttp://example.com/liveadmin/a/b, qiankun отреагирует на изменение маршрута, сопоставит соответствующее субприложение в соответствии с определенным activeRule, а затем субприложение возьмет на себя маршрут и загрузит ресурсы страницы, соответствующие субприложению. Подробный процесс реализации см. в исходном коде qiankun.Основная идея состоит в том, чтобы очистить HTML-код, возвращаемый записью субприложения.<script>tag , получить ресурсы Javascript модуля, а затем выполнить соответствующий Javascript через eval.

2.2 субприложение liveadmin

  1. В соответствии с практикой официальной документации qiankun экспортируйте соответствующую функцию ловушки жизненного цикла в файл входа подприложения.
if (window.__POWERED_BY_QIANKUN__) { // 注入 Webpack publicPath, 使得主应用正确加载子应用的资源
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

if (!window.__POWERED_BY_QIANKUN__) { // 独立访问启动逻辑
    bootstrapApp({});
}

export const bootstrap = async () => { // 启动前钩子
    await Promise.resolve(1);
};

export const mount = async (props) => { // 集成访问启动逻辑,接手主应用传递的数据
    bootstrapApp(props);
};

export const unmount = async (props) => {  // 卸载子应用的钩子
    props.container.querySelector('#j-look').remove();
};
  1. Измените конфигурацию упаковки Webpack.
output: {
    path: DIST_PATH,
    publicPath: ROOTPATH,
    filename: '[name].js',
    chunkFilename: '[name].js',
    library: `${packageName}-[name]`,
    libraryTarget: 'umd', // 指定打包的 Javascript UMD 格式
    jsonpFunction: `webpackJsonp_${packageName}`,
},
  1. Скрывайте элементы заголовка и боковой панели вложенного приложения при работе с интегрированным доступом.
const App = Regular.extend({
    template: window.__POWERED_BY_QIANKUN__
    ? `
        <div class="g-wrapper" r-view></div>
    `
    : `
        <div class="g-bd">
            <div class="g-hd mui-row">
                <AppHead menus={headMenus}
                    moreMenus={moreMenus}
                    selected={selectedHeadMenuKey}
                    open={showSideMenu}
                    on-select={actions.selectHeadMenu($event)}
                    on-toggle={actions.toggleSideMenu()}
                    on-logout={actions.logoutAuth}></AppHead>
            </div>
            <div class="g-main mui-row">
                <div class="g-sd mui-col-4" r-hide={!showSideMenu}>
                    <AppSide menus={sideMenus} 
                        selected={selectedSideMenuKey}
                        show={showSideMenu}
                        on-select={actions.selectSideMenu($event)}></AppSide>
                </div>
                <div class="g-cnt" r-class={cntClass}>
                    <div class="g-wrapper" r-view></div>
                </div>
            </div>         
        </div>  
    `,
    name: 'App',
    // ...
})
  1. При обработке интегрированного доступа блокируйте запросы данных разрешений и информации для входа и вместо этого получайте разрешения и данные меню из основного приложения, чтобы избежать избыточных HTTP-запросов и настроек данных.
if (props.container) { // 集成访问时,直接设置权限和菜单
    store.dispatch(setMenus(props.menus))
    store.dispatch({
        type: 'GET_PRIVILEGES_SUCCESS',
        payload: {
            privileges: props.privileges,
            menus: props.menus
        }
    });
} else { // 独立访问时,请求用户权限,菜单直接读取本地的配置
    MixInMenus(props.container);
    store.dispatch(getPrivileges({ userName: name }));
}
if (props.container) {  // 集成访问时,设置用户登录账户
    store.dispatch({
        type: 'LOGIN_STATUS_SUCCESS',
        payload: {
            user: props.account,
            loginType: 'OPENID'
        }
    });
} else { // 独立访问时,请求和设置用户登录信息
    store.dispatch(loginStatus());
}

  1. Изменения базы маршрутов при обработке доступа к интеграции

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

const start = (container) => {
    router.start({
        root: config.base,
        html5: true,
        view: container ? container.querySelector('#j-look') : Regular.dom.find('#j-look')
    });
};

2.3 увеличить подприложение

Аналогично тому, что делает субприложение liveadmin.

  1. Экспортируйте соответствующие хуки жизненного цикла.
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

const CONTAINER = document.getElementById('container');

if (!window.__POWERED_BY_QIANKUN__) {
    const history = createBrowserHistory({ basename: Config.base });
    ReactDOM.render(
        <Provider store={store()}>
            <Symbol />
            <Router path="/" history={history}>
                {routeChildren()}
            </Router>
        </Provider>,
        CONTAINER
    );
}

export const bootstrap = async () => {
    await Promise.resolve(1);
};

export const mount = async (props) => {
    const history = createBrowserHistory({ basename: Config.qiankun.base });
    ReactDOM.render(
        <Provider store={store()}>
            <Symbol />
            <Router path='/' history={history}>
                {routeChildren(props)}
            </Router>
        </Provider>,
        props.container.querySelector('#container') || CONTAINER
    );
};
export const unmount = async (props) => {
    ReactDOM.unmountComponentAtNode(props.container.querySelector('#container') || CONTAINER);
};
  1. Конфигурация упаковки Webpack.
output: {
    path: DIST_PATH,
    publicPath: ROOTPATH,
    filename: '[name].js',
    chunkFilename: '[name].js',
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
},
  1. При интеграции доступа удалите заголовок и боковую панель.
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-line
    return (
        <BaseLayout location={location} history={history} pms={pms}>
            <Fragment>
                {
                    curMenuItem && curMenuItem.block
                        ? blockPage
                        : children
                }
            </Fragment>
        </BaseLayout>
    );
}

  1. При интеграции доступа блокируйте разрешения и запросы на вход, а также получайте разрешения и данные меню, передаваемые основным приложением.
useEffect(() => {
    if (login.status === 1) {
        history.push(redirectUrl);
    } else if (pms.account) { // 集成访问,直接设置数据
        dispatch('Login/success', pms.account);
        dispatch('Login/setPrivileges', pms.privileges);
    } else { // 独立访问,请求数据
        loginAction.getLoginStatus().subscribe({
            next: () => {
                history.push(redirectUrl);
            },
            error: (res) => {
                if (res.code === 301) {
                    history.push('/login', {
                        redirectUrl,
                        host
                    });
                }
            }
        });
    }
});
  1. При интеграции доступа измените базу реактивного маршрутизатора.
export const mount = async (props) => {
    const history = createBrowserHistory({ basename: Config.qiankun.base });
    ReactDOM.render(
        <Provider store={store()}>
            <Symbol />
            <Router path='/' history={history}>
                {routeChildren(props)}
            </Router>
        </Provider>,
        props.container.querySelector('#container') || CONTAINER
    );
};

2.4 Интеграция разрешений (необязательный шаг)

  1. Как упоминалось выше, внешнее приложение связано с приложением разрешений PMS, тогда, если каждое внешнее приложение объединено через микро-интерфейс, и если каждое внешнее подприложение по-прежнему соответствует разрешению собственной PMS приложение разрешений, а затем стоящее в разрешении. С точки зрения администраторов необходимо обратить внимание на несколько приложений разрешений PMS, назначать разрешения и управлять ролями, что очень сложно для работы, например, различие страниц двух подприложений, и управление ролями двух подприложений с одним и тем же разрешением. Поэтому необходимо рассмотреть возможность унификации приложений разрешений PMS, соответствующих подприложениям.Только наш метод обработки описан здесь только для справки.
  2. Чтобы максимально сохранить исходный метод управления разрешениями (диспетчер разрешений отправляет код разрешения страницы в PMS через бэкдор внешнего интерфейса, а затем назначает разрешение страницы PMS), в сценарии микро-интерфейса, что необходимо сделать для интеграции разрешений можно описать следующим образом:
    1. Каждое подприложение сначала отправляет данные меню и кода разрешения этого проекта в соответствующие приложения разрешений PMS.
    2. Основное приложение загружает данные меню и кода разрешения каждого подприложения, изменяет данные каждого меню и код разрешения на данные приложения разрешения PMS, соответствующие основному приложению, а затем отправляет их в приложение разрешения PMS, соответствующее основному приложению. Разрешение PMS, соответствующее приложению, используется для унифицированного распределения и управления разрешениями в приложении.
  3. В нашей практике, чтобы диспетчеры разрешений по-прежнему не знали об изменениях, внесенных этим разделенным приложением, приложение разрешений appCode = live_backend PMS, соответствующее исходному приложению liveadmin, по-прежнему используется для назначения разрешений.Нам необходимо применить разрешение PMS, соответствующее к приложению liveadmin.Измените приложение разрешений PMS, соответствующее основному приложению lookcms, и создайте новое приложение разрешений PMS с appCode = live_legacy_backend для подприложения liveadmin, и новое подприложение повышения будет по-прежнему соответствовать PMS применение разрешения appCode = look_backend. Данные меню и кодов разрешений двух вышеуказанных подприложений соответственно передаются соответствующему приложению разрешений PMS в соответствии с пунктом 2, описанным на предыдущем шаге. Наконец, основное приложение lookcms одновременно получает меню внешнего подприложения и данные кода разрешения двух приложений разрешений PMS, appCode = live_legacy_backend и appCode = look_backend, и изменяет их на данные приложения разрешения PMS appCode. = live_backend, и отправляет его в PMS. Общий процесс показан на рисунке ниже. Слева — исходный дизайн системы, справа — измененный дизайн системы.

2.5 Развертывание

  1. Liveadmin и увеличение каждого используют внешнюю статическую систему развертывания Cloud Music для независимого развертывания, а основные приложения lookcms также развертываются независимо.
  2. Обработайте междоменную проблему основного приложения, обращающегося к ресурсам подприложения. В нашей практике, поскольку все они развернуты в одном домене, упаковка ресурсов подчиняется одному и тому же правилу домена.

2.6 Резюме

С тех пор мы завершили реализацию микроинтерфейса на основе qiankun LOOK в режиме прямой трансляции, в основном путем создания нового основного проекта, разделения обязанностей основного приложения и изменения подпроектов таким образом, чтобы подпроекты приложения могут быть интегрированы в основное приложение и доступны. , вы также можете сохранить исходную независимую функцию доступа. Общий процесс можно описать следующим образом:

3. Совместное использование зависимостей

Официальный qiankun не рекомендует конкретное решение для совместного использования зависимостей. Мы также провели некоторые исследования по этому вопросу. Вывод можно резюмировать следующим образом: для зависимостей общедоступных библиотек Javascript, таких как Regularjs и React, функции жизненного цикла подприложений могут быть загружены через внешние компоненты Webpack и qiankun.И плагин import-html-entry для решения, а для компонентов и других сценариев, требующих совместного использования кода, вы можете использовать плагин федерации модулей Webapck 5 для решения. Конкретные планы таковы:

3.1 Разобранные нами публичные зависимости делятся на две категории

3.1.1 Одной из них является базовая библиотека, такая как Regularjs, Regular-state, MUI, React, React Router и другие ресурсы, которые, как ожидается, не будут загружаться повторно в течение цикла доступа.

3.1.2 Другой тип — общедоступные компоненты.Например, компоненты React должны быть общими для подприложений, и нет необходимости копировать код между проектами.

3.2. Для вышеупомянутых двух типов зависимостей мы выполнили некоторую локальную практику, поскольку нет срочного спроса со стороны бизнеса, а Webpack 5 временно выпущен как стабильная версия (на момент написания этой статьи Webpack 5 выпустил релизную версию , и мы увидим конкретный бизнес в будущем.Будет ли требование онлайн в этой части функции), поэтому оно не было проверено в производственной среде, но вы можете поделиться методом обработки и результатами здесь.

3.2.1 Для первого типа публичных зависимостей мы реализуем общее ожидание: при интегрированном доступе основное приложение может динамически загружать библиотеку, от которой сильно зависит подприложение, а само подприложение больше не загружается. При независимом доступе само подприложение также может загружать нужные ему зависимости самостоятельно. Здесь необходимо решить два вопроса: а) как основное приложение собирает и динамически загружает зависимости подприложений б) как подприложение достигает различных представлений загрузки ресурсов во время интеграции и независимого доступа.

3.2.1.1. Первая проблема, нам нужно сохранить определение общих зависимостей, то есть определить общие ресурсы, от которых зависит каждое подприложение в основном приложении, и вставить их в глобальный хук жизненного цикла микроприложения qiankun перед загрузкой.<script>Метод тега загрузки ресурсов Javascript, требуемых текущим подприложением, справочный код выглядит следующим образом.

// 定义子应用的公共依赖
const dependencies = {
    live_backend: ['regular', 'restate'],
    look_backend: ['react', 'react-dom']
};
// 返回依赖名称
const getDependencies = appName => dependencies[appName];
// 构建script标签
const loadScript = (url) => {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    script.setAttribute('ignore', 'true'); // 避免重复加载
    script.onerror = () => {
        Message.error(`加载失败${url},请刷新重试`);
    };
    document.head.appendChild(script);
};
// 加载某个子应用前加载当前子应用的所需资源
beforeLoad: [
    (app) => {
        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
        getDependencies(app.name).forEach((dependency) => {
            loadScript(`${window.location.origin}/${$config.rootPath}${dependency}.js`);
        });
    }
],

Здесь мы также должны обратить внимание на создание соответствующих зависимых ресурсов через Webpack.Мы используем плагин copy-webpack-plugin для преобразования ресурсов выпуска в node_modules в пакеты, к которым можно получить доступ через независимые URL-адреса.

// 开发
plugins: [
    new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: JSON.stringify('development')
        }
    }),
    new webpack.NoEmitOnErrorsPlugin(),
    new CopyWebpackPlugin({
        patterns: [
            { from: path.join(__dirname, '../node_modules/regularjs/dist/regular.js'), to: '../s/regular.js' },
            { from: path.join(__dirname, '../node_modules/regular-state/restate.pack.js'), to: '../s/restate.js' },
            { from: path.join(__dirname, '../node_modules/react/umd/react.development.js'), to: '../s/react.js' },
            { from: path.join(__dirname, '../node_modules/react-dom/umd/react-dom.development.js'), to: '../s/react-dom.js' }
        ]
    })
],
// 生产
new CopyWebpackPlugin({
    patterns: [
        { from: path.join(__dirname, '../node_modules/regularjs/dist/regular.min.js'), to: '../s/regular.js' },
        { from: path.join(__dirname, '../node_modules/regular-state/restate.pack.js'), to: '../s/restate.js' },
        { from: path.join(__dirname, '../node_modules/react/umd/react.production.js'), to: '../s/react.js' },
        { from: path.join(__dirname, '../node_modules/react-dom/umd/react-dom.production.js'), to: '../s/react-dom.js' }
    ]
})

3.2.1.2 Что касается вторичной загрузки общедоступных зависимостей, когда подприложения интегрированы и доступны независимо, мы используем метод, который заключается в том, чтобы сначала скопировать общедоступные зависимости, определенные основным приложением, с помощью copy-webpack-plugin и html-webpack-externals- плагин Эти два плагина независимы внешним методом и не упакованы в бандл Webpack.При этом, через настройку плагина,<script>Добавьте к тегу атрибут ignore, а затем используйте его, когда qiankun загружает это подприложение.Плагин import-html-entry, от которого зависит qiankun, анализирует его.<script>теги, загруженные с атрибутом ignore, будут игнорироваться<script>тег, и само подприложение может нормально загружать этот ресурс Javascript при независимом доступе.

plugins: [
    new CopyWebpackPlugin({
        patterns: [
            { from: path.join(__dirname, '../node_modules/regularjs/dist/regular.js'), to: '../s/regular.js' },
            { from: path.join(__dirname, '../node_modules/regular-state/restate.pack.js'), to: '../s/restate.js' },
        ]
    }),
    new HtmlWebpackExternalsPlugin({
        externals: [{
            module: 'remoteEntry',
            entry: 'http://localhost:3000/remoteEntry.js'
        }, {
            module: 'regularjs',
            entry: {
                path: 'http://localhost:3001/regular.js',
                attributes: { ignore: 'true' }
            },
            global: 'Regular'
        }, {
            module: 'regular-state',
            entry: {
                path: 'http://localhost:3001/restate.js',
                attributes: { ignore: 'true' }
            },
            global: 'restate'
        }],
    })
],

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

3.2.2.1 Прежде всего, сценарий, определенный нашей практикой, таков: основное приложение lookcms предоставляет как компонент RButton на основе Regularjs, так и компонент TButton на основе React для совместного использования с субприложением liveadmin и субприложением увеличения соответственно.

3.2.2.2 Для основного приложения lookcms мы определяем подключаемый модуль объединения модулей Webpack5 следующим образом:

plugins: [
        // new BundleAnalyzerPlugin(),
        new ModuleFederationPlugin({
            name: 'lookcms',
            library: { type: 'var', name: 'lookcms' },
            filename: 'remoteEntry.js',
            exposes: {
                TButton: path.join(__dirname, '../client/exports/rgbtn.js'),
                RButton: path.join(__dirname, '../client/exports/rcbtn.js'),
            },
            shared: ['react', 'regularjs']
        }),
],

Определенные компоненты общего кода показаны на следующем рисунке:

3.2.2.3 Для субприложения liveadmin мы определяем подключаемый модуль объединения модулей Webpack5 следующим образом:

plugins: [
    new BundleAnalyzerPlugin(),
    new ModuleFederationPlugin({
        name: 'liveadmin_remote',
        library: { type: 'var', name: 'liveadmin_remote' },
        remotes: {
            lookcms: 'lookcms',
        },
        shared: ['regularjs']
    }),
],

С точки зрения использования подприложение должно сначала вставить исходный код в html какhttp://localhost:3000/remoteEntry.jsЗапись общих ресурсов основного приложения может быть вставлена ​​​​через html-webpack-externals-plugin, см. публичную зависимость внешней обработки подприложений выше.

Для загрузки внешних общих ресурсов подприложения загружаются асинхронно с помощью метода импорта Webpack, а затем вставляются в виртуальный DOM.Мы ожидаем реализовать Regularjs со ссылкой на решение React, предоставленное Webapck.К сожалению, Regularjs не имеет соответствующие основные функции помогают нам реализовать Lazy и Suspense.

После некоторых исследований мы выбрали условный рендеринг асинхронно загружаемых компонентов на основе API r-component, предоставляемого Regularjs.

Основная идея состоит в том, чтобы определить компонент Regularjs, который получает имя асинхронного компонента для загрузки из реквизита на этапе инициализации, загружает имя компонента, совместно используемое lookcms, с помощью метода импорта Webpack на этапе построения и добавляет его в RSuspense в соответствии с имя, определенное в свойствах. В компоненте логика отображения компонента RSusspense r-component также изменена для отображения компонента, привязанного по имени.

Из-за ограниченного написания грамматики Regularjs нам неудобно абстрагироваться от логики вышеперечисленных компонентов RSuspense, поэтому мы используем метод преобразования Babel: разработчик определяет оператор режима загрузки компонента и использует Babel AST для его преобразования. в компонент RSuspense. Наконец, используйте это в шаблоне RegularjsRSuspenseкомпоненты.

// 支持定义一个 fallback
const Loading = Regular.extend({
    template: '<div>Loading...{content}</div>',
    name: 'Loading'
});
// 写成一个 lazy 加载的模式语句
const TButton = Regular.lazy(() => import('lookcms/TButton'), Loading);
// 模版中使用 Babel AST 转换好的 RSuspense 组件
`<RSuspense origin='lookcms/TButton' fallback='Loading' />`

Преобразование синтаксиса с помощью Babel AST показано на следующем рисунке:

Фактический эффект работы показан на следующем рисунке:

3.2.2.4 Для вспомогательного приложения увеличения мы определяем подключаемый модуль федерации модулей Webpack 5 следующим образом:

plugins: [
    new ModuleFederationPlugin({
        name: 'lookadmin_remote',
        library: { type: 'var', name: 'lookadmin_remote' },
        remotes: {
            lookcms: 'lookcms',
        },
        shared: ['react']
    }),
],

С точки зрения использования, пожалуйста, обратитесь к официальной документации Webpack 5. Код выглядит следующим образом:

const RemoteButton = React.lazy(() => import('lookcms/RButton'));
const Home = () => (
    <div className="m-home">
        欢迎
        <React.Suspense fallback="Loading Button">
            <RemoteButton />
        </React.Suspense>
    </div>
);

Фактический эффект работы показан на следующем рисунке:

  1. Суммировать

4. Вопросы, требующие внимания

  1. Ресурсы разных источников Если ваше приложение реализует междоменную загрузку ресурсов другими способами, обратите внимание, что qiankun загружает все ресурсы подприложения через выборку, поэтому доступ к междоменным ресурсам необходимо осуществлять через CORS.
  2. HTML-теги для подприложений Возможно, некоторые атрибуты или некоторые функции установлены в теге html одного из ваших подприложений.Следует отметить, что qiankun удаляет теги html подприложений в фактической обработке.Поэтому, если требуется установить rem, пожалуйста использовать другие методы адаптации.

5. Будущее

  1. автоматизация Доступ к субприложению осуществляется через платформу, конечно, для этого требуется стандартный маршрут, по которому следует субприложение.
  2. совместное использование зависимостей Webpack 5 выпустил официальную версию, так что использование плагина федерации модулей можно поставить в график работы.

6. Резюме

Основываясь на реальных бизнес-сценариях, фон операции прямой трансляции LOOK использует qiankun для выполнения микро-интерфейсных инженерных разделений.Он бесперебойно работает в производственной среде в течение почти 4 месяцев.В процессе практики это правда, что установлены требования и реализована реализация доступа к qiankun.И есть некоторые трудности, возникающие на нескольких этапах развертывания приложения, такие как установление первоначальных требований, мы считали, что функция главного меню должна быть реализована, и часто встречающиеся ошибки в процессе доступа к qiankun, а также столкнулись с внутренними проблемами в процессе развертывания.Выбор и препятствия развертывания системы, к счастью, коллеги усердно работают, и проект может быть запущен и работать без сбоев.

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

Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, Любое несанкционированное воспроизведение статьи запрещено. Мы набираем front-end, iOS и Android круглый год.Если вы готовы сменить работу и любите облачную музыку, присоединяйтесь к нам на grp.music-fe(at)corp.netease.com!