Предварительные знания
Предыдущая статья:Внедрение внешнего микросервиса с 0 (на)упоминается в,single-spaПринцип заключается в том, чтобы поставитьlink/scriptэтикетки и<div id="app"></div>Вставить в основной проект, и суть этой операции — динамическая загрузкаjsа такжеcss.
динамическая нагрузкаjsмы используемsystem.js, с этим плагином нам нужно только изменить подпроектapp.jsПросто разоблачите его.
Эта статья основана наДемонстрация одного спа на GitHubИзменено, так что лучше изучить этоdemo, кроме того, эта статья основана на последнихvue-cli4разработка.
Шаги реализации Single-spa-vue
Достигаемый эффект заключается в том, что подпроекты разрабатываются и развертываются независимо друг от друга, и, кстати, их также можно интегрировать в основной проект.
Новый мастер-проект навигации
-
vue-cli4Использовать напрямуюvue create navкоманда для созданияvueпроект.
Следует отметить, что маршрутизация элемента навигации должна использовать режим истории.
- Исправлять
index.htmlдокумент
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>home-nav</title>
<!-- 配置文件注意写成绝对路径:/开头,否则访问子项目的时候重定向的index.html,相对目录会出错 -->
<script type="systemjs-importmap" src="/config/importmap.json"></script>
<!-- 预请求single-spa,vue,vue-router文件 -->
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" as="script" crossorigin="anonymous" />
<link rel="preload" href="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js" as="script" crossorigin="anonymous" />
<link rel="preload" href="https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js" as="script" crossorigin="anonymous" />
<!-- 引入system.js相关文件 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/system.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/amd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/named-exports.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/6.1.1/extras/use-default.min.js"></script>
</head>
<body>
<script>
(function() {
System.import('single-spa').then(singleSpa => {
singleSpa.registerApplication(
'appVueHistory',
() => System.import('appVueHistory'),
location => location.pathname.startsWith('/app-vue-history/')
)
singleSpa.registerApplication(
'appVueHash',
() => System.import('appVueHash'),
location => location.pathname.startsWith('/app-vue-hash/')
)
singleSpa.start();
})
})()
</script>
<div class="wrap">
<div class="nav-wrap">
<div id="app"></div>
</div>
<div class="single-spa-container">
<div id="single-spa-application:appVueHash"></div>
<div id="single-spa-application:appVueHistory"></div>
</div>
</div>
<style>
.wrap{
display: flex;
}
.nav-wrap{
flex: 0 0 200px;
}
.single-spa-container{
width: 200px;
flex-grow: 1;
}
</style>
</body>
</html>
- Файлы конфигурации для подпроектов и URL общедоступных файлов
config/importmap.json:
{
"imports": {
"appVue": "http://localhost:7778/app.js",
"appVueHistory": "http://localhost:7779/app.js",
"single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js",
"vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js",
"vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js"
}
}
Ремонт подпроекта
Маршрутизация в хеш-режиме Проект Vue
Если это недавно разработанный проект, вы можете использовать его в первую очередьvue-cli4генерироватьvueпроект, маршрут используетhashмодель.
1. Установите плагин (что он делает дальше):
Если это старый проект, вам необходимо установить следующие три плагина:
npm install systemjs-webpack-interop -S
npm install single-spa-vue -S
npm install vue-cli-plugin-single-spa -D
Если это новый проект, вы можете использовать следующую команду:
vue add single-spa
Примечание. Эта команда перезапишет ваш main.js, не используйте эту команду для старых проектов.
Команда делает четыре вещи:
-
(1) Установка
single-spa-vueплагин -
(2) Установка
systemjs-webpack-interopплагин и сгенерироватьset-public-path.js -
(3) Модификация
main.js -
(4) модификация
webpackКонфигурация (разрешить междоменное использование, отключить горячее обновление, удалитьsplitChunksЖдать)
2. Добавьте две переменные среды
из-заsingle-spaВ моде также есть среды разработки и производства, поэтому есть 4 среды: обычная разработка,single-spaразработка, нормальная упаковка,single-spaПакет. Но нам нужны только два файла переменных среды, чтобы различать их.Создайте новый файл переменных среды в корневом каталоге:
.env.devSingleSpaфайлы (различать нормальную разработку иsingle-spaразработка шаблона):
NODE_ENV = development
VUE_APP__ENV = singleSpa
.env.singleSpaфайл (различать обычную упаковку иsingle-spaупаковка картины):
NODE_ENV = production
VUE_APP__ENV = singleSpa
3. Измените файл записи
single-spaЕдинственным отличием от обычного режима разработки является входной файл. Плагины, которые необходимо ввести в файле ввода (vuex,vue-router,axios,element-uiд.) совершенно одинаковы, разница в том, что нормальное развитиеnew Vue(options),single-spaэто позвонитьsingleSpaVue(Vue,options)функционируют и будут иметь три жизненных циклаexport.
Поэтому я по-прежнему пишу общие части двух режимов вmain.jsи экспортировать объекты конфигурации, необходимые для обоих режимов:
import store from "./store";
import Vue from 'vue';
import App from './App.vue';
import router from './router';
const appOptions = {
render: (h) => h(App),
router,
store,
}
Vue.config.productionTip = false;
export default appOptions;
новыйindex.js(Файл входа в обычном режиме):
import appOptions from './main';
import './main';
import Vue from 'vue';
new Vue(appOptions).$mount('#app');
новыйindex.spa.js(single-spaфайл входа схемы):
import './set-public-path'
import singleSpaVue from 'single-spa-vue';
import appOptions from './main';
import './main';
import Vue from 'vue';
const vueLifecycles = singleSpaVue({
Vue,
appOptions
});
const { bootstrap, mount, unmount } = vueLifecycles;
export { bootstrap, mount, unmount };
вindex.spa.jsвнутриset-public-path.js:
import { setPublicPath } from 'systemjs-webpack-interop'
//模块的名称必须和system.js的配置文件(importmap.json)中的模块名称保持一致
setPublicPath('appVueHash')
4. Измените конфигурацию упаковки (vue.config.js)
single-spaЕдинственная разница между режимом и обычным режимом заключается в файле входа, а остальные одинаковы. То есть после упаковки толькоapp.jsЕсли файлы разные, можно ли повторно использовать другие файлы и можно ли развернуть два режима в одном пакете?
Ответ да: я выполняю сначала при упаковкеsing-spaупаковка, затем выполните упаковку в обычном режиме и, наконец,single-spaупакованныйapp.jsФайл копируется в обычный корневой каталог упакованного файла. Просто возьми этоdistСодержимое может быть развернуто,single-spaДля синхронизации обновлений модификация не требуется.
Следует отметить, что у файла не может быть хеш-значения.Если у файла нет хеш-значения, сервер должен сам сгенерировать хэш-значение для установки кеша.
const CopyPlugin = require('copy-webpack-plugin');
const env = process.env.VUE_APP__ENV; // 是否是single-spa
const modeEnv = process.env.NODE_ENV; // 开发环境还是生产环境
const config = {
productionSourceMap: false,//去掉sourceMap
filenameHashing: false,//去掉文件名的hash值
};
const enteyFile = env === 'singleSpa' ? './src/index.spa.js' : './src/index.js';
//正常打包的app.js在js目录下,而single-spa模式则需要在根目录下。
//打包时会从dist-spa/js目录将app.js拷贝到正常打包的根目录下,所以不用管,只需要判断single-spa的开发模式即可
const filename = modeEnv === 'development' ? '[name].js' : 'js/[name].js';
chainWebpack = config => {
config.entry('app')
.add(enteyFile)
.end()
.output
.filename(filename);
if(env === 'singleSpa'){
//vue,vue-router不打包进app.js,使用外链
config.externals(['vue', 'vue-router'])
}
}
if(env === 'singleSpa'){
Object.assign(config, {
outputDir: 'dist-spa',
devServer: {
hot: false,//关闭热更新
port: 7778
},
chainWebpack,
})
}else{
Object.assign(config, {
chainWebpack,
configureWebpack: modeEnv === 'production' ? {
plugins: [
//将single-spa模式下打包生成的app.js拷贝到正常模式打包的主目录
new CopyPlugin([{
from: 'dist-spa/js/app.js',
to: ''
}])
],
} : {},
})
}
module.exports = config;
Эффект упакованного файла:
вjs/app.jsгенерируется в обычном режиме, аindex.htmlв том же каталогеapp.jsдаdist-spa/js/app.jsскопировано, даsingle-spaВходной файл режима, другие файлы используются повторно.
5. Измените команду упаковки (package.json)
single-spaВ режиме разработки/пакета необходимо изменить переменные среды, а обычныйbuildКоманда изменена на: упаковать два раза подряд, и можно добиться того же процесса упаковки и развертывания, что и в оригинале.
"scripts": {
"spa-serve": "vue-cli-service serve --mode devSingleSpa",
"serve": "vue-cli-service serve",
"spa-build": "vue-cli-service build --mode singleSpa",
"usual-build": "vue-cli-service build",
"build": "npm run spa-build && npm run usual-build",
"lint": "vue-cli-service lint"
},
single-spaиспользование в целях развитияnpm run spa-serve, нормальное развитие остается неизменным.
Упакуйте и продолжайте использоватьnpm run build,ПотомdistФайлы в каталоге можно развернуть на сервере подпроекта.
Проект Vue для маршрутизации в режиме истории
Поскольку мы принудительно используем другой префикс для маршрута подпроекта (/app-vue-history),существуетhashшаблон в порядке, потому чтоhashВ режиме переход маршрутизации будет изменен толькоurlизhashзначение, не будет измененоpathстоимость.historyрежим вам нужно сказатьvue-router,/app-vue-history/Это префикс маршрутизации проекта, а прыжок нужен только для модификации последней части, в противном случае скачок маршрутизации напрямую охватывает все пути. Тогда этот элемент конфигурацииbaseАтрибуты:
const router = new VueRouter({
mode: "history",
base: '/',//默认是base
routes,
});
Метод тоже очень простой, судите по переменным окружения,single-spaв режимеbaseсобственность/app-vue-history, нормальный режим не меняется.
Однако, поскольку мы упаковали и повторно использовали все, кромеapp.jsДругие файлы, поэтому только файл записи может отличить среду, решение:
router/index.jsФайл маршрута не экспортирует экземпляр объекта маршрута, но функция:
const router = base => new VueRouter({
mode: "history",
base,
routes,
});
а такжеmain.jsФайл маршрута больше не импортируется, а импортируется отдельно в файле записи.
Входной файл для обычного режимаindex.js:
import router from './router';
const baseUrl = '/';
appOptions.router = router(baseUrl);
single-spaФайл входа схемыindex.spa.js:
import router from './router';
const baseUrl = '/app-vue-history';
appOptions.router = router(baseUrl);
Анализ некоторых принципов
Роль и преимущества system.js
system.jsРоль заключается в динамической загрузке модулей по запросу. Если все наши подпроекты используютvue,vuex,vue-router, каждый товар упакован один раз, это будет очень расточительно.system.jsможет сотрудничатьwebpackизexternalsсвойства, настроить эти модули как внешние ссылки, а затем реализовать загрузку по требованию:
Конечно, вы также можете напрямую использоватьscriptОтметьте эти общедоступныеjsВсе они импортируются, но это приведет к потерям. Например, подпроект A используетvue-routerа такжеaxios, но не использовалvuex, подпроект A обновляется, он по-прежнему будет запрашиватьvuex, это пустая трата,system.jsбудет загружаться по запросу.
При этом подпроекты упаковываются вumdФормат,system.jsОн может быть загружен по требованию подпроектов.
Что делает плагин systemjs-webpack-interop (Адрес GitHub)
предыдущий постКак упоминалось во введении, прямое введение подпроектовjs/cssПодсистемы могут визуализироваться, но динамически генерироватьсяHTMLсередина,img/video/audioПуть к другим файлам является относительным, поэтому он не может быть загружен. И решение 1: изменитьvue-cli4изpublicPathустановить полный абсолютный путьhttp://localhost:8080/Вот и все.
Функция этого плагина заключается в преобразовании подпроектаpublicPathподвергатьsystem.js,system.jsСопоставьте с файлом конфигурации на основе имени проекта (importmap.json), затем проанализируйте настроенныйurl, присвойте префиксpublicPath.
ТакpublicPathКак настроить динамически?официальный сайт вебпакаМетоды, приведенные в:webpackвыставляет__webpack_public_path__Вы можете напрямую изменить значение глобальной переменной.
systemjs-webpack-interopНесколько скриншотов исходного кода (public-path-system-resolve.js):
Так вот почемуsingle-spaвходной файлapp.jsИ чтобыindex.htmlКаталог тот же, потому что он напрямую перехватилapp.jsпуть какpublicPath.
Что делает плагин single-spa-vue (Адрес GitHub)
Основная функция этого плагина — помочь нам написатьsingle-spaТребуются три периодических события:bootstrap,mount,unmount.
существуетmountЧто делает цикл, так это генерирует то, что нам нужно<div id="app"></div>, разумеется, имя id получается по названию проекта:
Тогда прямо здесьdivвнутреннее воплощениеvue:
Поэтому, если мы хотим, чтобы содержимое подэлемента находилось в нашей пользовательской области (по умолчанию вставляется вbody), один из способовdivНапишите:
home-nav/public/index.html:
Другой способ — изменить эту часть кода, чтобы вставить ее туда, где мы хотим ее вставить, а неbody.
unmountцикл выгружает созданный экземплярvueи опустелDOM, хотите достичьkeep-aliveДля эффекта мы должны изменить эту часть кода (описано позже)
Роль плагина vue-cli-plugin-single-spa (Адрес GitHub)
Эта пробка в основном используется для командыvue add single-spaПри выполнении перезаписатьmain.jsи генерироватьset-public-path.js, при изменении вашегоwebpackконфигурация. но выполнитьnpm install vue-cli-plugin-single-spa -Dкоманда, она просто перезаписывает вашуwebpackконфигурация.
его модификацияwebpackСконфигурированный исходный код:
module.exports = (api, options) => {
options.css.extract = false
api.chainWebpack(webpackConfig => {
webpackConfig
.devServer
.headers({
'Access-Control-Allow-Origin': '*',
})
.set('disableHostCheck', true)
webpackConfig.optimization.delete('splitChunks')
webpackConfig.output.libraryTarget('umd')
webpackConfig.set('devtool', 'sourcemap')
})
}
Возвращаясь к исходной точке, получаемsingle-spaСамое главное: динамический импорт подпроектовjs/css, но ты его не находишь, ты только видишь его до концаjsВведение без упоминанияcss,ТакcssЧто с файлами? ответoptions.css.extract = false.
vue-cli3Согласно официальному сайту, это значениеfalse, который не генерируется отдельноcssфайл иjsфайлы упакованы вместе, что позволяет нам заботиться только оjsВведение файла можно сделать, но это тожеcssПроблема загрязнения похоронена.
Еще одна конфигурация — разрешить междоменный доступ, как и та, что упоминалась в начале статьи.system.jsТребовать, чтобы подпроекты были упакованы какumdform, и настраивается.
Есть и более критическая конфигурация:webpackConfig.optimization.delete('splitChunks'), при нормальных обстоятельствах файлы, которые мы упаковали, являются дополнением к файлам входаapp.js, и еще один файлchunk-vendors.js, этот файл содержит какие-то общедоступные сторонние плагины, так что подпроект имеет два файла входа (или загрузить эти два файла одновременно), поэтому его можно только удалитьsplitChunks.
Примечания и другие подробности
- переменная среды
При развертывании удалите файл записи (app.js), другие файлы маршрутизации повторно используют обычно упакованные файлы, поэтому переменные среды необходимо внедрить в глобальное использование файлом записи.
index.spa.jsдокумент:
appOptions.store.commit('setSingleSpa',true);
- Лучше всего установить фиксированный порт для разработки подпроекта.
Избегайте частого изменения файлов конфигурации, установите фиксированный специальный порт и старайтесь избегать конфликтов портов.
- single-spa закрывает горячее обновление
Режим разработки все еще развивается нормально, ноsingle-spaСовместная отладка должна закрыть горячее обновление, иначе локальноеwebsocketбудет продолжать сообщатьfailed.
single-spaВо время разработки я обнаружил, что горячее обновление работает нормально.
- URL-адрес импорта внешнего файла в index.html должен быть указан как абсолютный путь.
Файл конфигурации должен быть записан как абсолютный путь, иначе маршрут будет перенаправлен обратно в основной проект при доступе к подпроекту.index.html, URL-адрес внутри относительного каталога будет неправильным.
home-nav/public/index.html:
<script type="systemjs-importmap" src="/config/importmap.json"></script>
- Как реализовать «поддержание активности»
Проверитьsingle-spa-vueИсходный код можно найти вunmountжизненный цикл будетvueПримерdestroy(разрушен) и опустошенDOM. достигатьkeep-alive, нам просто нужно удалитьdestroyи не пустойDOM, а затем используйтеdisplay:noneчтобы скрыть и показать подэлементыDOMВот и все.
function unmount(opts, mountedInstances) {
return Promise
.resolve()
.then(() => {
mountedInstances.instance.$destroy();
mountedInstances.instance.$el.innerHTML = '';
delete mountedInstances.instance;
if (mountedInstances.domEl) {
mountedInstances.domEl.innerHTML = ''
delete mountedInstances.domEl
}
})
}
- Как избежать загрязнения css
Мы используем конфигурациюcss.extract = trueПозже,cssБольше не создавать файлы отдельно, а упаковывать вjsВнутри сгенерированные стили завернуты вstyleВ теге после удаления подпроекта файл стиля не удаляется, слишком много стилей может привести к загрязнению стиля.
Решение:
Способ 1: соглашение об именах +css-scope+ Удалить глобальный стиль
Способ 2: удалить стиль при удалении приложенияstyleЭтикетки (для изучения)
Если вы должны написать глобальные переменные, вы можете использовать метод, похожий на «скиннинг»: дать подпроектbody/htmlДобавьте уникальный идентификатор (для обычной разработки и развертывания), затем добавьте этот идентификатор перед глобальным стилем иsingle-spaрежим нужно изменитьsingle-spa-vue,существуетmountцикл, чтобы датьbody/htmlДобавьте этот уникальный идентификатор вunmountЦикл удален, так что этот глобальный css может гарантированно вступить в силу только для этого проекта.
- как избежать конфликта js
Прежде всего, мы должны стандартизировать разработку: в компонентеdestroyЖизненный цикл удаляет глобальные свойства/события, и другим способом являетсяwindowОбъект делает снимок, а затем восстанавливает свое предыдущее состояние при размонтировании.
- Как взаимодействуют подпроекты
можно использоватьlocalstorageОбщайтесь с пользовательскими событиями.localstorageОбычно он используется для обмена информацией о входе пользователя и т. д., в то время как настраиваемые события обычно используются для обмена данными в реальном времени, такими как количество сообщений.
//1、子组件A 创建事件并携带数据
const myCustom = new CustomEvent("custom",{ detail: { data: 'test' } });
//2、子组件B 注册事件监听器
window.addEventListener("custom",function(e){
//接收到数据
})
//3、子组件A触发事件
window.dispatchEvent(myCustom);
- Как управлять разрешениями подсистемы
Один из способов — скрыть входную навигацию прямо из системы без разрешения, а затем напрямую ввестиurlПосле входа подпроект все еще будет загружен, но страница 403 может отображаться после того, как подпроект решит, что у него нет разрешения. Видно, что входной файл, соответствующий подсистеме, записан наjsonВ файл, то вы не можете прочитать это.jsonНу или для пользователей, которые хотят реализовать разные разрешенияjsonКонфигурация другая.
Мы можем динамически генерироватьscriptЭтикетка:
//在加载模块之前先生成配置json
function insertNewImportMap(newMapJSON) {
const newScript = document.createElement('script')
newScript.type = 'systemjs-importmap';
newScript.innerText = JSON.stringify(newMapJSON);
const test = document.querySelector('#test')
test.insertAdjacentElement('beforebegin',newScript);
}
//内容从接口获取
const devDependencies = {
imports: {
"navbar": "http://localhost:8083/app.js",
"app1": "http://localhost:8082/app.js",
"app2": "http://localhost/app.js",
"single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js",
"vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js",
"vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js"
}
}
insertNewImportMap(devDependencies);
Суммировать
Если вы не хотите самостоятельно создавать статический файловый сервер узла, я рекомендую программное обеспечение: XAMPP
заполнить в статьеdemoАдрес файла:GitHub.com/gongshun/четыре…
-
существующие на данный момент проблемы
-
Переходы маршрутизации между подпроектами не могут быть удалены
urlизhashзначение, например, от'/app1/#/home'перенаправить на'/app2/', хеш-значение все равно будет сохранено:'/app2/#/', который в настоящее время не действует, но может повлиять на решение о маршрутизации подпроектов. -
Даже если подпроекты находятся в одном стеке технологий, версия фреймворка не может быть унифицирована.Хотя в настоящее время существует операция по извлечению общего фреймворка, это может быть сложно контролировать в реальной работе.
-
Во время общей разработки и отладки проекта, если проект A является средой разработки, а проект B — средой упаковки, будет сообщено об ошибке при переключении маршрутизации туда и обратно, когда обе среды являются средами разработки или обе являются производственными средами. (причина неизвестна)
-
-
следующие шаги
- Исследуйте Али
qiankunРамка -
reactИ проект трансформацииangularТрансформация проекта, хотя принцип и похож, но детали будут другими
- Исследуйте Али
Наконец, спасибо всем за чтение, и я желаю вам счастливого Нового года!
Если у вас есть какие-либо вопросы, пожалуйста, укажите, что следующая статья была обновлена:Внедрение внешнего микросервиса single-spa с 0 (ниже)