От 0 до 1, чтобы построить микроинтерфейсную архитектуру, подходящую для вашей компании

JavaScript
От 0 до 1, чтобы построить микроинтерфейсную архитектуру, подходящую для вашей компании

1. Почему его следует преобразовать в модель разработки микроинтерфейса?

использовать фон

  • Как команда я в основном делаю проект по обслуживанию в правительственной информационной технологии конструктивных, то есть мы часто говоримtoG. Этот проект завершен командой R & D, состоящей из почти дюжины компаний-победителей.Каждая компания отвечает за подсистему бизнеса, и каждая система сложна, проект, который я делал раньше. Подсистемы нашей компании заняли полтора года от проекта, такой огромный проект многие члены команды делают впервые. Таким образом, в опыте проекта и в начале недели каждый потребитель столкнулся с огромными проблемами для завершения стыковки, конечно, есть необходимость расти;
  • Front-end архитектура проекта такова, что один из пакетов берет на себя роль единого портала, а затем подсистемы бизнеса других пакетов используютiframeОн каким-то образом встроен в систему портала, а стек технологий каждого пакета объединен на одной странице.Vue+ElementUI.
  • Поскольку компания также выполняет рядSAASВ то же время также необходимо полностью отделить существующий набор проектов, обслуживающих правительство, от зависимости других подрядчиков, чтобы реализовать сотрудничество с компанией.SAASбесшовное соединение химических продуктов.
  • этот наборSAASВсе подсистемы химического продукта совместно используют набор пользовательских систем, а затем проходятжилецОтправляйтесь в разные провинции и деловые места, чтобы приземлиться. Все наши подсистемыподелиться одной системой входа,такая же структура меню,тот же заголовок,тот же переключатель вкладок,Просто бизнес-логика каждой подсистемы разная. Что касается внешнего интерфейса, то если для разработки по-прежнему используется традиционная одностраничная модель, если необходимо реализовать 20 провинций, то нам необходимо разработать 20 наборов одностраничных проектов. некоторые общие меню, пользовательские системы, заголовок и т. д. необходимо многократно копировать, как стоимость разработки интерфейса, так и стоимость последующего обслуживания огромны. Поэтому, учитывая все аспекты, доступ к микроинтерфейсу может быть хорошим выбором.

Вот простой демонстрационный пример продукта SAAS компании.

Авторизоваться

Страница входа в систему

подсистема

2. Как построить микро-интерфейсный проект, подходящий для системы вашей компании?

рынок

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

  • В чем разница между микроинтерфейсом и традиционным одностраничным приложением?
традиционная одностраничная

микро интерфейс

  • Как собрать набор микрофронтендов, подходящих для вашей компании от 0 до 1?
  1. Из приведенного выше рисунка ясно видно, что ядро ​​микроинтерфейса на самом деле состоит иззагрузчик модулей(enter),

ОдинБазовый модуль (навбар) (логин + меню + шапка и другие публичные части),различные модули подсистемыЭти три части составляются. Здесь я также создал 4 фронтенд-проекта. , соответственно:Вход (ввод), панель навигации (база), подсистема один, подсистема два.

  1. Входное приложение (enter)пройти черезsystemjsПо разным подсистемамидентификатор маршрутазагружать упакованные по требованиюapp.jsдокумент.

Модернизация приложения ввода

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="importmap-type" content="systemjs-importmap">
    <link rel="stylesheet" href="/style/common.css">
    <title>微前端入口</title>
    <script type="systemjs-importmap">
        {
          "imports": <%= JSON.stringify(htmlWebpackPlugin.options.meta.all) %>
        }
    </script>
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <link rel="preload" href="/js/single-spa.min.js" as="script" crossorigin="anonymous" />
    <link rel="preload" href="/js/vue.min.js" as="script" crossorigin="anonymous" />
    <script src='/js/minified.js'></script>
    <script src="/js/import-map-overrides.js"></script>
    <script src="/js/system.min.js"></script>
    <script src="/js/amd.min.js"></script>
    <script src="/js/named-exports.js"></script>
    <script src="/js/named-register.min.js"></script>
    <script src="/js/use-default.min.js"></script>
</head>
<body>
    <script>
        (function() {
            Promise.all([
                System.import('single-spa'),
                System.import('vue'),
                System.import('vue-router'),
                System.import('element-ui')]).then(function (modules) {
                    
            var singleSpa = modules[0];
            var Vue = modules[1];
            var VueRouter = modules[2];
            var ElementUi = modules[3];
            Vue.use(VueRouter)
            Vue.use(ElementUi)

            <% for (let app in htmlWebpackPlugin.options.meta.route) { %>
                singleSpa.registerApplication(
                    '<%= app %>',
                    function () {
                        return System.import('<%= htmlWebpackPlugin.options.meta.route[app] %>')
                    },
                    function(location) {
                        <% if (app !== 'navbar') { %>
                            return location.pathname.split('/')[1] === '<%= app %>'
                        <% } else { %>
                            return true
                        <% } %>
                    })
            <% } %>
            
            singleSpa.start();
        })
      })()
    </script>
    <import-map-overrides-full show-when-local-storage="overrides-ui"></import-map-overrides-full>
</body>
</html>
  • Во-первых, передайте плагины, от которых зависит микро-интерфейс.scriptПредставлять.
  • Тогда мы сначала проходимsystem.jsПоместите в проект некоторые общедоступные плагины, такие какvue, vue-маршрутизатор, ElementUiДождитесь глобального представления.
  • Наконец, с помощьюwebpackизhtmlWebpackPluginв плагинеmetaАтрибуты, пакет каждой подсистемы бизнесаapp.jsПройдите и разгрузитесь. Конечно, мы также можем напрямую передать метод обхода без использованияpromiseЧтобы ввести асинхронно, метод обхода используется для введения здесь в основном для лучшего постобслуживания и лучшей масштабируемости.

конфигурация веб-пакета

new HtmlWebpackPlugin({
    filename: 'index.html',
    template: resolve(__dirname, '../index.ejs'),
    inject: false,
    title: 'title',
    minify: {
        collapseWhitespace: false
    },
    meta: {
        all: Object.assign(config[0], config[1]),
        route: config[1],
        outputTime: new Date().getTime()
    }
})
  • подсистемаapp.jsпройти черезconfigдля загрузки в разных средахapp.js, даже используяNodeпеременные среды, чтобы различатьразвивать,тестовое задание,Производствотри среды.

среда разработки

module.exports = {
    "navbar": '//localhost:8002/navbar/app.js',
    "children1": '//localhost:8003/children1/app.js',
    "children2": '//localhost:8004/children2/app.js',
};

тестовая среда

const host = process.env.HOST;
module.exports = {
    "navbar": host + '/navbar/app.js',
    "children1": host + '/children1/app.js',
    "children2": host + '/children2/app.js',
};

Производственная среда

const host = process.env.HOST;
module.exports = {
    "navbar": host + '/navbar/app.js',
    "children1": host + '/children1/app.js',
    "children2": host + '/children2/app.js',
};

здесьhostТо есть доменное имя доступа тестовой и производственной среды, которое мы настраиваем в записи для упаковки и сборки.

"build": "rimraf dist && cross-env NODE_ENV=production HOST=//spa.caoyuanpeng.com:9001 webpack --config build/webpack.prod.config.js"

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

output: {
    path: resolve(__dirname, '../dist'),
    publicPath: '/',
    filename: '[name].js',
    chunkFilename: 'js/[name]-[chunkhash:6].js',
    library: 'app',
    libraryTarget: 'umd'
}
  1. О трансформации приложения navbar

Пакет webpack.base.config.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HappyPack = require('happypack');
// 通过os动态获取开启子进程的数量
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
const BasePlugins = require('./plugins');

const { resolve } = path;
const isDevMode = process.env.NODE_ENV === 'development';

module.exports = {
    devtool: process.env.NODE_ENV !== 'production' ? 'source-map' : 'none',
    // 入口
    entry: {
        app: ['webpack-hot-middleware/client', resolve(__dirname, main)]
    },
    // 出口
    output: {
        filename: 'app.js',
        path: resolve(__dirname, '../dist'),
        chunkFilename: 'js/[name]-[chunkhash:6].js',
        publicPath: isDevMode ? '/' : '/navbar',
        // library: 'navbar',
        libraryTarget: 'umd'
    },
    externals: isDevMode ? {} : ['vue', 'vue-router', 'element-ui'],
    plugins: [
        ...BasePlugins,
        new MiniCssExtractPlugin({
            filename: 'css/[name].[hash:6].css',
            chunkFilename: 'css/[id].[hash:6].css'
        }),
        new HappyPack({
            /*
             * 必须配置
             */
            // id 标识符,要和 rules 中指定的 id 对应起来
            id: 'babel',
            // 需要使用的 loader,用法和 rules 中 Loader 配置一样
            // 可以直接是字符串,也可以是对象形式
            loaders: ['babel-loader?cacheDirectory'],
            // 使用共享进程池中的子进程去处理任务
            threadPool: happyThreadPool
        })
    ],
    module: {
        rules: [
            {
                test:/\.css$/,
                use:[
                    {
                        loader: isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader 
                    },
                    'css-loader', {
                    loader: 'postcss-loader',
                    options: {
                        plugins: [require('autoprefixer')]
                    }
                    }
                ]
              },
              {
                test: /\.less$/,
                use:[
                    {
                        loader: isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader
                    },
                    'css-loader',
                    'less-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [require('autoprefixer')]
                        }
                    }
                ]
              },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: ['happypack/loader?id=babel'],
            },
            {
                test: /\.(jpg|jpeg|png|gif)$/,
                loaders: 'url-loader',
                exclude: /node_modules/,
                options: {
                    limit: 8192,
                    outputPath: 'img/',
                    name: '[name]-[hash:6].[ext]'
                }
            },
            {
                test: /\.(woff|woff2|svg|eot|ttf)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            outputPath: 'fonts/',
                            name: '[name].[ext]'
                        }
                    }
                ]
            },
            {
                test: /\.vue$/,
                use: ['vue-loader']
            }
        ]
    },
    resolve: {
        extensions: ['.js', 'json', '.less', '.css', '.vue'],
        alias: {
            vue$: 'vue/dist/vue.common.js',
            '@': resolve(__dirname, '../src'),
            'pages': resolve(__dirname, '../src/pages'),
        }
    }
};
  • мы вПроизводственная средаа такжетестовое кольцоиспользуется при упаковкеexternalsБудуvue, vue-маршрутизатор, элемент-интерфейсудалены, т.к. эти плагины находятся в нашемenterбыл введен в , поэтому вnavbarОн используется только в среде разработки в системе, а тестовую и производственную среды не нужно вводить повторно.
  • publicPath: '/navbar'Определите упакованный виртуальный путь, который должен быть указан здесь и не может иметь то же имя, что и каждая подсистема бизнеса;
  • libraryTarget: 'umd', все еще используюUMDрежим упаковки

Входное преобразование системы навигации

base.js

import '@babel/polyfill';
import { setPublicPath } from 'systemjs-webpack-interop';
import Vue from 'vue';
import VueRouter from 'vue-router';
import Element from 'element-ui';
import singleSpaVue from 'single-spa-vue';
import routes from '../router';

const baseFn = () => {
    // 默认控制台不输出vue官方打印日志
    Vue.config.productionTip = false;
    // 使用devtools调试
    Vue.config.devtools = true;
    // 注册navbar
    setPublicPath('navbar');
    // 生成vue-router实例
    const router = new VueRouter({
        mode: 'history',
        routes
    });
    
    Vue.use(VueRouter);
    Vue.use(Element);
    // appOptions抽离
    const appOptions = {
        render: h => <div id="navbar">
            <router-view></router-view>
        </div>,
        router
    };
    // 注册single-spa-vue实例
    const vueLifecycles = singleSpaVue({
        Vue,
        appOptions
    });

    return vueLifecycles;
}

export default baseFn;

здесь черезsystemjs-webpack-interopсерединаsetPublicPathрегистрnavbar, затем поставьтеvueэкземпляр, за которым следуетrouter,vuexустановлены наsingleSpaVueначальство.

main.dev.js

import BaseFn from './base';

BaseFn();

main.prod.js

import BaseFn from './base';

const vueLifecycles = BaseFn();

export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;

производственная среда будетvueLifecyclesподняться набутстрап, монтировать, размонтироватьЭти три односпальных цикла находятся на крючке.

Преобразование маршрутизации страницы

import View from '@/pages/components/view';
const Template = () => import(/* webpackChunkName: "index" */ '@/pages/index');

const routes = [
    {
        path: '/navbar',
        component: Template,
        meta: {
            title: '菜单'
        },
        children: [
            {
                path: '*',
                component: View,
                meta: {
                    title: ''
                }
            }
        ]
    }
];

export default routes;

Нам нужно добавить единый префикс доступа к началу маршрута.navbar, цель - входenterВы можете получить доступ к префиксам через разные маршруты для загрузки различных по запросуapp.js. ==Здесь есть одна вещь, на которую следует обратить особое внимание, а именно: наша цель — всегдаnavbarПриложение загружается без разрушения,Здесь вам нужно установить путь под детьми к*==

  1. Преобразование приложения подсистемы

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

output: {
    filename: 'app.js',
    path: resolve(__dirname, '../dist'),
    chunkFilename: 'js/[name]-[chunkhash:6].js',
    publicPath: isDevMode ? '/' : '/children2',
    library: 'children2',
    libraryTarget: 'umd'
}

publicPathбыть настроенным наchildren2,так же какlibraryбыть настроенным наchildren2.

Затем идет маршрутизация, которая должна установить префикс единого доступа страницы подсистемы наchildren2

import View from '@/pages/components/view';
const Template = () => import(/* webpackChunkName: "index" */ '@/pages/index');
const Detail = () => import(/* webpackChunkName: "detail" */ '@/pages/test');

const routes = [
    {
        path: '/children2',
        component: View,
        meta: {
            title: '子应用'
        },
        children: [
            {
                path: 'index',
                component: Template,
                meta: {
                    title: '首页'
                }
            },
            {
                path: 'detail',
                component: Detail,
                meta: {
                    title: '详情'
                }
            }
        ]
    }
];

export default routes;
  1. После загрузки app.js подсистемы single-spa-vue генерирует разные экземпляры vue в соответствии с разными маршрутами.

вот простоdemoДемонстрация, заинтересованные друзья могут щелкнуть эту ссылку, чтобы испытать ее.

  • Прежде всего, в приведенной выше демонстрации мы ясно видим, что при первом доступе к доменному имени для загрузки панели навигации сначала будет сгенерирован экземпляр панели навигации (здесь две кнопки подсистема 1 и подсистема 2 используются для отображения содержимое навигационной панели), когда мы нажмем кнопку подсистемы 1, будет автоматически сгенерирован экземпляр Children1, страница будет отображать содержимое подсистемы 1, а экземпляр навигационной панели останется и не будет уничтожен.
  • Затем, когда мы нажимаем кнопку подсистемы 2, автоматически создается экземпляр children2, и страница отображает содержимое подсистемы 2. В этот момент экземпляр children1 будет уничтожен, а экземпляр navbar останется без уничтожения.
  • На этом этапе мы можем завершить создание микроинтерфейсного приложения от начала до конца.

3. В чем разница между развертыванием микроинтерфейсов в производственных средах и традиционным одностраничным развертыванием?

  • Для традиционных одностраничных приложений нам просто нужно передатьjenkinsилиdockerчтобы упаковать нашу статику, а затем скопировать упакованный статический пакет на статический сервер через скрипт и, наконец, передатьNginxдля запуска агента.
  • Развертывание микрофронтендов несколько отличается от нашего традиционного одностраничного, потому что мы по сути используем основное приложение для загрузки разных подприложений, то есть основное приложение и базовое приложение, главное приложение и подприложения взаимозависимый от. Поэтому при развертывании нам нужно найти способ очистить эти соответствия через скрипт сборки, прежде чем мы сможем плавно запустить микро-фронтенд.
  • еслиjenkinsизpipelineДля сборки нам нужно обратить внимание на то, что каждый раз перед сборкой основного приложения следует удалять предыдущий старый каталог сборки, а затем копировать новый основной каталог приложения. Мы можем создатьмягкое соединениеВы можете связать каталог файлов основного приложения с тем же уровнем, что и каталог подприложения.

вот черезjenkinsУпаковывается и копируется в указанный нами каталог статических файлов.

Действуя на главном приложении Nginx

server {
    listen       9001;
    server_name  spa.caoyuanpeng.com;

    location / {
        root /home/single-spa-vue;
        try_files       $uri $uri/ /index.html;
        index index.html;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

  1. Нашему nginx нужно только найти файл index.html в приложении ввода.
  2. Путь к файлу подсистемы на сервере должен совпадать с идентификационным именем маршрутизации нашей подсистемы, иначе файл не может быть загружен в систему.
  3. Подсистемы не требуют дополнительной отдельной настройкиnginx, вам нужно только настроить приложение для входа.

4. Каковы преимущества и недостатки преобразования в микро-интерфейс?

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

  • Чтобы уменьшить объем упаковки системы, средний пакет подсистемы составляет менее нескольких сотен тысяч, а все общедоступные файлы js и файлы css необходимо загрузить только один раз.
  • Благодаря совместимости с различными технологическими стеками мы можем использовать несколько технологических фреймворков (React, Vue, AngularJS) на одной странице, и нет необходимости обновлять страницу.
  • Если есть подсистема, которая хочет получить доступ к микроинтерфейсу, стоимость доступа невелика, и нет необходимости рефакторить код.
  • Код каждой подсистемы может быть загружен по требованию без траты дополнительных ресурсов.
  • Каждая подсистема может быть отдельным проектом git и развертываться независимо.
  • Между разными git-проектами можно собирать произвольные модули на основе каждого независимого маршрута страницы.
  • Пользовательский опыт лучше, и пользователь может загружать несколько подсистем одновременно без восприятия.

недостаточный

  • Поскольку в процессе загрузки будет сгенерировано несколько экземпляров Vue, необходимо сформулировать подробные спецификации глобального стиля, иначе это вызовет различные загрязнения стиля.
  • Внешний используется в подсистеме для извлечения некоторых общих плагинов, в то же время нам нужно избегать загрязнения, вызванного такими конструкторами, как vue, vue-router, element-ui и т. д.
  • Средства защиты маршрутизации подсистем не должны влиять друг на друга.
  • Не все сценарии подходят для архитектуры микро-фронтенда, эти сценарии подходят, когда у вас есть достаточное количество проектов, все из которых представляют собой похожие одностраничные проекты, и вам нужно объединить различные функции подсистемы в большую систему.

5. Какие лучшие прорывы могут быть сделаны на основе микроинтерфейса в будущем?

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

6. Резюме

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

images.jpeg