- Published on
Webpack Module Federation에 대해 알아보자
- Author
- Name
- yceffort
이 글을 위주로 번역한 글이며, 추가적으로 micro frontend에 대한 개념도 넣어두었습니다.
Table of Contents
federated application 만들어보기
./src/App
을 app_one_remote
라고 선언하였다. 이는 다른 애플리케이션에서 실행될 수 있다.
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
module.exports = {
// other webpack configs...
plugins: [
new ModuleFederationPlugin({
name: 'app_one_remote',
remotes: {
app_two: 'app_two_remote',
app_three: 'app_three_remote',
},
exposes: {
AppContainer: './src/App',
},
shared: ['react', 'react-dom', 'react-router-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
chunks: ['main'],
}),
],
}
애플리케이션 헤드에, app_one_remote.js
를 불러오도록 했다. 이렇게 하면 다른 웹팩 런타임에 연결되고, 런타임에 오케이스트레이션 계층을 프로비저닝 할 수 있다. 이는 특별히 설계된 웹팩 런타임과 진입점이다. 이는 일반적인 애플리케이션 진입점과 다르게, 몇 kb에 불과하다.
<head>
<script src="http://localhost:3002/app_one_remote.js"></script>
<script src="http://localhost:3003/app_two_remote.js"></script>
</head>
<body>
<div id="root"></div>
</body>
App One
에서 App Two
에 있는 코드를 사용하고 싶다면,
const Dialog = React.lazy(() => import('app_two_remote/Dialog'))
const Page1 = () => {
return (
<div>
<h1>Page 1</h1>
<React.Suspense fallback="Loading Material UI Dialog...">
<Dialog />
</React.Suspense>
</div>
)
}
export default Page1
라우터는 일반적인 표준과 비슷하다.
import { Route, Switch } from 'react-router-dom'
import Page1 from './pages/page1'
import Page2 from './pages/page2'
import React from 'react'
const Routes = () => (
<Switch>
<Route path="/page1">
<Page1 />
</Route>
<Route path="/page2">
<Page2 />
</Route>
</Switch>
)
export default Routes
App Two에서는 Dialog를 내보낼 것이며, 이는 위에서 봤던 것 처럼 App One에서 사용한다.
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app_two_remote',
filename: 'remoteEntry.js',
exposes: {
Dialog: './src/Dialog',
},
remotes: {
app_one: 'app_one_remote',
},
shared: ['react', 'react-dom', 'react-router-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
chunks: ['main'],
}),
],
}
루트 앱은 이런 모양이다.
import React from 'react'
import Routes from './Routes'
const AppContainer = React.lazy(() => import('app_one_remote/AppContainer'))
const App = () => {
return (
<div>
<React.Suspense fallback="Loading App Container from Host">
<AppContainer routes={Routes} />
</React.Suspense>
</div>
)
}
export default App
import React from 'react'
import { ThemeProvider } from '@material-ui/core'
import { theme } from './theme'
import Dialog from './Dialog'
function MainPage() {
return (
<ThemeProvider theme={theme}>
<div>
<h1>Material UI App</h1>
<Dialog />
</div>
</ThemeProvider>
)
}
export default MainPage
App Three
의 경우, <App>
에서 실행되는 것이 없이 독립되어 있으므로, 아래와 같이 처리하면 된다.
new ModuleFederationPlugin({
name: "app_three_remote",
library: { type: "var", name: "app_three_remote" },
filename: "remoteEntry.js",
exposes: {
Button: "./src/Button"
},
shared: ["react", "react-dom"]
}),
트위터에 제작자가 공유해준 실제 코드를 살펴보자.
네트워크 탭을 살펴보면, 세 코드가 모두 다른 번들에 존재하고 있음을 알 수 있다.
의존성 중복이 존재하지 않는다. shared
옵션에 나와있듯, remote
는 host
의 의존성에 의존하게 된다. 만약 호스트에 해당 의존성이 존재하지 않는다면, 리모트는 알아서 다운로드 할 것이다.
vendor
나 다른 모듈을 shared
에 추가하는 것은 확장성에 그다지 좋지 못하다. AutomaticModuleFederationPlugin
를 제공하여, 웹팩 코어 외부에 있는 코드들을 관리할 수 있도록 할 것이다.
Server Side Rendering
Module Federation은 브라우저 node 모든 환경에서 동작한다. 단지 서버 빌드가 commonjs 라이브러리 타겟을 사용하기만 하면 된다.
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'container',
library: { type: 'commonjs-module' },
filename: 'container.js',
remotes: {
containerB: '../1-container-full/container.js',
},
shared: ['react'],
}),
],
}
Module Federation에 대한 다양한 예제를 아래에서 살펴볼 수 있다.
- https://github.com/module-federation/module-federation-examples
- https://github.com/module-federation/next-webpack-5
- https://github.com/ScriptedAlchemy/mfe-webpack-demo
- https://github.com/ScriptedAlchemy/webpack-external-import
결과적으로 하나의 큰 애플리케이션을 여러개의 독립된 애플리케이션으로 만든 다음, 다이나믹 로딩을 하듯이 필요한 순간에 필요한 컴포넌트 (소스)를 불러오게 한다는 개념인 것 같다. webpack5 에 포함될 예정이라고 하니, 정식 출시 될 때 실제 동작하는 예제를 만들어보고 고민해봐야겠다.