마이크로 프런트엔드(MFE) 구현 방법
이 가이드는 Bahmni에서 자체 MFE를 구현하는 과정을 단계별로 설명합니다. 예시로 DiagnosticsDashboard라는 단일 구성 요소를 내보내는 diagnostics 마이크로 프런트엔드를 만든다고 가정합니다.
1단계: MFE 저장소 생성
좋은 시작점으로 bahmni-microfrontend-reference 저장소를 복제할 수 있습니다. README.md에는 필수 정보로 저장소를 설정할 때 따라야 할 점검 목록이 있습니다.
점검 목록을 완료하면 ModuleFederation 플러그인의 모듈 이름은 bahmni_diagnostics가 되고 DiagnosticsDashboard는 같은 플러그인의 exposes 속성에 포함되어야 합니다.
2단계: MFE 제공 전략 결정
yarn build를 실행하면 dist/federation/ 폴더에 빌드 파일이 생성됩니다. 이 폴더의 파일은 웹 서버에서 제공해야 하며, 그 주소가 원격 URL이 됩니다.
2.1: 프록시를 사용하는 Docker 컨테이너
참조 저장소는 역방향 프록시 기반의 단일 컨테이너로 bahmni-docker 설정 안에서 MFE를 제공한다고 가정합니다. 이 방식을 사용한다면 다음 단계를 따릅니다.
2.1.1: bahmni/microfrontend-diagnostics라는 이미지를 빌드하도록 GitHub 워크플로와 Dockerfile을 업데이트합니다.
2.1.2: 사용하는 docker-compose 설정에 적절한 항목을 추가합니다. 사용 사례에 따라 bahmni-docker 또는 그 포크일 수 있습니다. 결과적으로 diagnostics 같은 이름의 컨테이너가 생성되어야 합니다. 로컬 개발용 볼륨 마운트를 포함한 예시는 아래와 같습니다.
diagnostics:
image: bahmni/microfrontend-diagnostics:latest
profiles: ["emr", "bahmni-lite", "bahmni-mart"]
volumes:
- "${DIAGNOSTICS_PATH:?}/dist/federation/:/usr/local/apache2/htdocs/diagnostics"2.1.3: 프록시 설정에 컨테이너 항목을 추가합니다. bahmni-proxy를 사용한다면 resources/bahmni-proxy.conf에 역방향 프록시 규칙을 추가합니다.
이렇게 하면 Apache 폴더를 /diagnostics 프록시에서 접근할 수 있습니다. https://<bahmni-host>/diagnostics/remoteEntry.js에 접근되는지 확인합니다.
3단계: openmrs-module-bahmniapps에 MFE 모듈 생성
3.1: 새 Angular 모듈을 담을 소스 파일 생성
mkdir micro-frontends/src/diagnostics
touch micro-frontends/src/diagnostics/index.js/* micro-frontends/src/diagnostics/index.js */
// Follow the below module naming convention
angular.module('bahmni.mfe.diagnostics', [/* add any dependencies */]);3.2: 새 모듈의 WebPack 진입점 추가
/* micro-frontends/webpack.config.js */
module.exports = {
// ... other stuff
entry: {
// ...
diagnostics: "./src/diagnostics/index.js"
}
}위 단계를 완료하면 ui/app/micro-frontends-dist/diagnostics.min.js와 `ui/app/micro-frontends-dist/diagnostics.min.css로 빌드되는 새 WebPack 모듈이 추가됩니다.
3.3: 테스트용 모듈 모의 구현
자체 저장소에서 MFE를 완전히 테스트한다고 가정하면 Bahmni 단위 테스트에서는 이 모듈을 모의 구현하는 편이 간단합니다. ui/test/__mocks__/micro-frontends.js에서 설정합니다.
/* ui/test/__mocks__/micro-frontends.js */
// add an empty module as a mock
angular.module('bahmni.mfe.diagnostics', []);4단계: 호스팅 React 구성 요소 생성
4.1: MFE용 ModuleFederation 구성 추가
동적 가져오기에 사용할 원격 URL을 설정합니다.
/* micro-frontends/webpack.config.js */
module.exports = {
// ... other stuff
plugins: [
// ...
new ModuleFederationPlugin({
// ...
remotes: {
// ...
// IF using proxy, do this
// Add your MFE with the federated module name and proxy path
"@openmrs-mf/diagnostics": remoteProxiedAtHostDomain({ name: "bahmni_diagnostics", path: "diagnostics"})
// if not using proxy, add the static URL instead of using remoteProxiedAtHostDomain
}
})
]
}4.2: 원격 구성 요소를 불러오는 호스팅 React 구성 요소 생성
/* micro-frontends/src/diagnostics/Dashboard.jsx */
import PropTypes from "prop-types";
import React, { Suspense, lazy } from "react";
export function Dashboard(props) {
// on run, this will fetch remoteEntry.js and load the DiagnosticsDashboard component from there
const LazyComp = lazy(() => import("@openmrs-mf/diagnostics/DiagnosticsDashboard"));
return (
<>
<Suspense fallback={<p>Loading...</p>}>
<LazyComp
hostData={props.hostData}
hostApi={props.hostApi}
/>
</Suspense>
</>
);
}
// Without propTypes, react2angular won't render the component
Dashboard.propTypes = {
hostData: PropTypes.shape({
// Set up your input data shape as needed here, below is an example
patient: PropTypes.shape({
uuid: PropTypes.string.isRequired,
}).isRequired,
}),
hostApi: PropTypes.shape({
// Set up your output (callbacks) shape as needed. below is an example
onConfirm: PropTypes.func,
}),
};5단계: Angular 구성 요소 등록
3단계에서 만든 Angular 모듈에는 4단계의 호스팅 구성 요소와 연결되는 래퍼 Angular 구성 요소를 추가해야 합니다. 두 가지 방법이 있습니다.
옵션 A(권장): React2AngularBridgeBuilder 추상화 사용
규칙을 사용하여 래퍼 빌드 과정을 단순화하는 추상화를 제공합니다. micro-frontends/src/utils/bridge-builder.js를 확인하십시오.
/* micro-frontends/src/diagnostics/index.js */
import { React2AngularBridgeBuilder } from "../utils/bridge-builder";
import { Dashboard } from 'Dashboard.jsx';
// Extract module name to make it easier
const MODULE_NAME = "bahmni.mfe.diagnostics"
angular.module(MODULE_NAME, []);
const builder = new React2AngularBridgeBuilder({
moduleName: MODULE_NAME,
// use the following convention mfe<camelCaseNameOfModule>
componentPrefix: "mfeDiagnostics",
});
builder.createComponent("Dashboard", Dashboard);
// The above registers an angular component named 'mfeDiagnosticsDashboard'옵션 B: 수동 방식
래퍼 빌드 과정을 더 세밀하게 제어하려면 micro-frontends/src/utils/bridge-builder.js의 추상화가 내부에서 수행하는 작업을 따라 수동으로 구현할 수 있습니다.
/* micro-frontends/src/diagnostics/index.js */
import { Dashboard } from 'Dashboard.jsx';
import { react2angular } from "react2angular";
// Extract module name to make it easier
const MODULE_NAME = "bahmni.mfe.diagnostics"
angular.module(MODULE_NAME, []);
angular.module(MODULE_NAME)
.component('mfeDiagnosticsDashboard', react2angular(Dashboard));
// The above registers an angular component named 'mfeDiagnosticsDashboard'6단계: Angular 모듈에서 MFE 사용
특정 Angular 모듈에서 MFE를 사용하려면 모듈을 의존성으로 포함하고 React 및 필요한 빌드 파일을 HTML 진입점에 추가합니다. Clinical 모듈을 예로 들겠습니다.
<!-- ui/app/clinical/index.html -->
<html>
<head>
<!-- common mfe styles -->
<link rel="stylesheet" href="../micro-frontends-dist/shared.min.css"/>
<!-- global specific styles of diagnostics mfe -->
<link rel="stylesheet" href="../micro-frontends-dist/diagnostics.min.css"/>
<!-- build:css clinical.min.css -->
<!-- DO NOT ADD MFE STYLES INSIDE THE GRUNT BUILD BLOCK -->
<!-- endbuild -->
</head>
<body>
<!-- Adding react since an micro-frontend will be used -->
<script src="../components/react/react.production.min.js"></script>
<script src="../components/react-dom/react-dom.production.min.js"></script>
<!-- form-controls also needs react so make sure you include react before this -->
<script src="../components/bahmni-form-controls/helpers.js"></script>
<script src="../components/bahmni-form-controls/bundle.js"></script>
<!-- shared js including polyfills -->
<script src="../micro-frontends-dist/shared.min.js"></script>
<!-- load the mfe js -->
<script src="../micro-frontends-dist/diagnostics.min.js"></script>
<!-- build:js clinical.min.js -->
<!-- DO NOT ADD MFE STYLES INSIDE THE GRUNT BUILD BLOCK -->
<!-- endbuild -->
</body>
</html>text index.html 안에서 JavaScript와 CSS 파일의 순서와 위치를 올바르게 지정하는 것이 매우 중요합니다. Grunt가 이미 축소된 파일을 다시 선택하여 축소하려는 빌드 마커 위치가 있으며, 이는 MFE 빌드 실패를 일으킵니다. 주석의 지침을 따르십시오.
---------- Add shared css first
---------- Then add mfe css
---------- IMPORTANT: Make sure no mfe css is inside a build block like this
---------- Include the react packages if not already present
---------- Add the shared JS for mfes
---------- Add the JS of the mfe
---------- IMPORTANT: Make sure no mfe js is inside a build block like this. All MFE code needs
to be ABOVE this block마지막으로 init.js 파일에 모듈을 의존성으로 포함합니다.
/* ui/app/clinical/init.js */
// ...
angular.module('bahmni.clinical', [
// other dependencies
'bahmni.mfe.diagnostics'
])그러면 다음과 같이 템플릿에서 구성 요소를 렌더링할 수 있습니다.
<!-- ui/app/clinical/dashboard/views/dashboard.html -->
<mfe-diagnostics-dashboard host-data="hostData" host-api="hostApi">
</mfe-diagnostics-dashboard>정보 흐름 관리
현재 호스트와 MFE 구성 요소 사이의 통신을 위한 이벤트 버스 아키텍처는 없습니다. 규칙으로 hostData와 hostApi 키를 사용하여 통신합니다.
호스트에서 MFE로 데이터 전달
원격 MFE는 표준 React props를 통해 호스트 Angular 애플리케이션의 데이터를 받을 수 있습니다. 규칙상 이러한 입력은 hostData라는 단일 prop 객체로 감쌉니다. 사용 예시는 다음과 같습니다.
// in the controller
$scope.hostData = {
patient: {
// patient info
},
otherData: "// other data"
}<!-- in the template -->
<mfe-example-component1 host-data="hostData"></mfe-example-component1>// in the React entry component
export function Component1(props) {
return <div>{props.hostData.patient.name}</div>
}참고: AngularJS는 camelCase 바인딩을 kebab-case로 매핑하므로 hostData prop은 HTML의 host-data 바인딩에 연결됩니다.
MFE 이벤트에 대한 호스트의 반응
현재는 호스트 앱이 객체여야 하는 hostApi라는 규칙 기반 prop을 통해 MFE에 콜백을 전달합니다. 사용 예시는 다음과 같습니다.
// in the controller
$scope.hostApi = {
onClose: function() {
closeModal();
},
onConfirm: function(id) {
service.confirmValues(id);
}
}<!-- in the template -->
<mfe-example-component1 host-api="hostApi"></mfe-example-component1>// in the React entry component
export function Component1(props) {
// *See notes
return <button onClick={props.hostApi?.onClose?.()>Close</button>
}hostApi 관련 중요 참고 사항
react2angular 어댑터는 hostApi 바인딩 안의 함수를 해석하는 데 한 틱이 더 걸립니다. 따라서 propTypes에서 hostApi 함수를 필수로 지정해도 React가 undefined의 함수에 접근하는 등의 참조 오류를 발생시킬 수 있습니다. 이를 피하려면 선택적 연결 연산자(?.)를 사용하는 것이 좋습니다.
Bahmni Wiki · CC BY-SA 4.0