마이크로 프론트엔드 아키텍쳐를 잘 활용하기 위해서 몇가지 지켜야하는 필수사항들이 있다.
개발환경, 배포환경에 따라 웹팩 설정이 다르기 떄문에 공용,개발,배포 웹팩 설정을 따로 만들어주도록 하자.
module.exports = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-react", "@babel/preset-env"],
plugins: ["@babel/plugin-transform-runtime"],
},
},
},
],
},
};
// 공용 웹팩 설정을 현재 설정가 합칠 수 있게해준다.
const { merge } = require("webpack-merge");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const commonConfig = require("./webpack.common");
const devConfig = {
mode: "development",
devServer: {
port: 8081,
historyApiFallback: {
index: "index.html",
},
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};
module.exports = merge(commonConfig, devConfig);
이제 index.html 과 index.js 를 각자 public, src 폴더를 만들어서 생성해주고 아래의 명령어를 start에 지정해준다. npm start 명령어를 입력하면 웹팩 설정 파일을 개발 설정을 바라보도록 한다는 설정이다.
"scripts": {
"start": "webpack serve --config config/webpack.dev.js"
},
그 다음엔 마케팅 프로젝트에 리액트를 추가 해보도록 하자. 프레임워크 없이 모듈페더레이션을 했을 떄도 bootstrap.js란 파일 생성하고 index.js에서 import해서 사용했는데 그걸 그대로 이 프로젝트에도 적용시켜보자.
이제 필요한 UI 들을 기존 리액트 프로젝트에서 개발하듯이 App.js 안에서 개발하고 bootstrap.js 에서 import한다.
import React from "react";
import ReactDOM from "react-dom";
// 기존 리액트 프로젝트의 APP
import App from "./App";
// Mount function to start up the app
const mount = (el) => {
ReactDOM.render(<App />, el);
};
// If we are in development and in isolation,
// call mount immediately
if (process.env.NODE_ENV === "development") {
const devRoot = document.querySelector("#_marketing-dev-root");
if (devRoot) {
mount(devRoot);
}
}
// We are running through container
// and we should export the mount function
export { mount };
이제 컨테이너에도 똑같은 설정을 해주지만 bootstrap 파일만 좀 다르게 설정해준다.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.querySelector("#root"));
이제 두 프로젝트를 module federation을 통해 연결해주는 설정이다. 컨테이너는 remotes, 그 외 다른 모듈들은 exposes 옵션을 통해 컨테이너에서 모듈들을 가져와서 보여줄 수 있도록 노출시켜주자. 여기서 shared 옵션을 통해 공통 모듈들을 서로 공유해서 모듈의 번들 사이즈를 줄여주도록 할 수 있다.
const { merge } = require("webpack-merge");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const commonConfig = require("./webpack.common");
const packageJson = require("../package.json");
const devConfig = {
mode: "development",
devServer: {
port: 8080,
historyApiFallback: {
index: "index.html",
},
},
plugins: [
new ModuleFederationPlugin({
name: "container",
remotes: {
marketing: "marketing@http://localhost:8081/remoteEntry.js",
},
// 프로젝트간의 공통 모듈 공유
shared: packageJson.dependencies,
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};
module.exports = merge(commonConfig, devConfig);
const { merge } = require("webpack-merge");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const commonConfig = require("./webpack.common");
const packageJson = require("../package.json");
const devConfig = {
mode: "development",
devServer: {
port: 8081,
historyApiFallback: {
index: "index.html",
},
},
plugins: [
new ModuleFederationPlugin({
name: "marketing",
filename: "remoteEntry.js",
exposes: {
"./MarketingApp": "./src/bootstrap",
},
// 프로젝트간의 공통 모듈 공유
shared: packageJson.dependencies,
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};
module.exports = merge(commonConfig, devConfig);
마지막으로 컨테이너의 App.js에서 marketing 프로젝트를 가져와서 보여줄 것이다.
React만 사용했던 나는 여기서 의문점이 들었다. MarketingApp을 왜 바로 component형태 노출시키지 않고 mount같이 함수 형태로 노출시키는 이유는 뭘까?
이유는 단순하다, jsx형식으로 component를 불러와 render하는 방식은 오직 React라는 라이브러리에서만 사용하는 방식이다, micro-frontend을 제대로 활용하기 위해서는 Vue, Angular 같은 다양한 프레임워크를 한 프로젝트로 합쳐서 보여줘야한다. 그렇기위해서는 jsx형식이 아닌 다른 프로젝트에서도 쉽게 적용될 수 있는 함수형태로 노출을 시켜서 받아오는 것이다.
import { mount } from "marketing/MarketingApp";
import React, { useRef, useEffect } from "react";
export default () => {
const ref = useRef(null);
useEffect(() => {
mount(ref.current);
}, []);
return <div ref={ref}></div>;
};
import React from "react";
import MarketingApp from "./components/MarketingApp";
export default () => {
return (
<div>
<h1>Hi there</h1>
<hr />
<MarketingApp />
</div>
);
};
이제 npm start를 해보면 결과물이 잘 나오는것을 확인 할 수 있다. 이걸로 제일 기본적인 monorepo형식의 mfa를 구현해보았다.