- Published on
Webpack Module Federation ์ง์ ํด๋ณด๊ธฐ
- Author
- Name
- yceffort
https://yceffort.kr/2020/09/webpack-module-federation ์์ ์ด์ด์ง๋ค.
webpack 5๊ฐ ๋ฐํ ๋๋ฉด์ ๋์์ module federation๋ ์ง์ ํด๋ณผ ์ ์๊ฒ ๋์๋ค. ํ๋ฒ ์ง์ ์ ์ฉํด๋ณด๋ฉด์ ์ ๋ง๋ก ๊ฒ์ ์ฒด์ธ์ ๊ฐ ๋ ์ ์๋์ง ์ดํด๋ณด์.
ํด๋น ์์ ํ๋ก์ ํธ ์ ์ฅ์๋ ์ฌ๊ธฐ๋ค.
react v17๊ณผ webpack 5๋ฅผ ๋ฐํ์ผ๋ก, ์์ฃผ ๊ธฐ์ด์ ์ธ ์ธํ ๋ง ํด์ ๋น ๋ฅด๊ฒ ๊ฐ๋ฐ์ ์งํํด๋ณด์๋ค.
main ์ค์
์ผ๋จ ๋ฉ์ธ ํ๋ก์ ํธ๊ฐ ์๊ณ , ์ฌ๊ธฐ์ ๊ธฐ์ ์๋ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ ธ๋ค ์ฐ๋ ๋ชจ์ต์ ์์ํด๋ณด๋ฉด์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด์. main์ module federation์ผ๋ก ์๋น๋๋ ๋ค๋ฅธ ํ๋ก์ ํธ๋ฅผ ๊ฐ์ ธ๋ค๊ฐ ์ฐ๋ federation์ ์ค์ฌ์ด๋ผ๊ณ ๋ณด๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 3001,
},
output: {
publicPath: 'http://localhost:3001/',
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'main',
remotes: {
app1: 'app1',
},
shared: ['react', 'react-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
}
ModuleFederationPlugin์ ์ฌ์ฉํ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ด ํ๋ฌ๊ทธ์ธ์ ContainerPlugin
๊ณผ ContainerReferencePlugin
๋ฅผ ํฉ์น ๊ฐ๋
์ด๋ผ๊ณ ๋ณด๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์ฌ๊ธฐ๋ ๋จ์ํ expose ํ ๋ค๋ฅธ federation์ ๊ฐ์ ธ๋ค ์ฐ๋ ์ญํ ๋ง ํ๊ธฐ ๋๋ฌธ์, exposes
๋ฅผ ํ๊ธฐ ์๊ณ ์๋ค.
app1 ์ค์
main
์์ ๊ฐ์ ธ๋ค ์ธ ์ค์ ์ปดํฌ๋ํธ๋ฅผ exposeํ๋ ๊ณณ์ด๋ค.
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 3002,
},
output: {
publicPath: 'http://localhost:3002/',
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'app1',
library: { type: 'var', name: 'app1' },
filename: 'remoteEntry.js',
exposes: {
'./Counter': './src/components/counter/index.jsx',
},
shared: ['react', 'react-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
}
main
๊ณผ ์ฐจ์ด์ ์ exposes
๊ฐ ์๋ค๋ ๊ฒ์ด๋ค. ์ฌ๊ธฐ์์๋ ๊ฐ๋จํ Counter
๋ฅผ ๋ด๋ณด๋ด๋๋ก ํ๊ณ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ ๊ฒ ๋ด๋ณด๋ธ Counter
๋ฅผ https://localhost:3000/remoteEntry.js
์์ ์๋น์ค ํ๋๋ก ์ค์ ํด์ฃผ์๋ค.
main
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Main App</title>
</head>
<body>
<div id="root"></div>
<script src="http://localhost:3002/remoteEntry.js"></script>
</body>
</html>
์๊น ์๋นํ๊ธฐ๋ก ์์ฑํด๋ remoteEntry.js
๋ฅผ ๋ก๊ฒจ์ค๋ ๋ชจ์ต์ด๋ค. ๋ฌผ๋ก ๋ ๋น ๋ฅด๊ฒ ๋ง๋ค๊ธฐ ์ํด์๋ async ๋ฑ์ ์ฌ์ฉํ ์ ๋ ์๋ค.
bootstrap.js
์ด๋ฆ์ด bootstrap
์ธ ์ด์ ๋ ๊ณต์ ๋ฌธ์์์ ๊ทธ๋ ๊ฒ ํ๊ณ ์๊ธธ๋ ๊ทธ๋ ๊ฒ ํ๋ค. ๐ ๋ป๊ณผ๋ ์ฐ๊ด์ด ์์๋ฏ.
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom'
const Counter = React.lazy(() => import('app1/Counter'))
function App() {
return (
<>
<h1>Hello from React component</h1>
<Suspense fallback="Loading Counter...">
<Counter title={'hello, counter'} />
</Suspense>
</>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
React
์ lazy
์ suspense
๋ฅผ ํ์ฉํ์ฌ app1
์์ exposeํ Counter
๋ฅผ ๊ฐ์ ธ๋ค ์ฐ๊ณ ์๋ค.
๊ฒฐ๊ณผ
์นด์ดํฐ๊ฐ ์ ๋์ค๊ณ ์๊ณ
์ ์์ ์ผ๋ก remoteEntry
์์ ๊ฐ์ ธ๋ค ์ฐ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ ๋ ์ปดํฌ๋ํธ ๋ชจ๋ share
๋ก ['react', 'react-dom']
์ ์ฐ๊ณ ์์๋๋ฐ, ์ด๊ฒ ์ญ์ ์ค๋ณต๋์ง ์๊ณ main
์์ ๋ฌถ์ด์ ์ฐ๊ณ ์๋ ๊ฒ์ ์ ์ ์์๋ค.
์ข์ ์ ๋ด์ง๋ ๊ธฐ๋ํ๋ ๋ฏธ๋
์์ฆ ์ ํ์ด๋ผ๊ณ ํ๋ Micro Frontend๋ฅผ ๋ฌ์ฑํ ์ ์๋ ์ข์ ๋ฐฉ๋ฒ ์ค ํ๋ ์ธ ๊ฒ ๊ฐ๋ค. ํ๋์ ์ฑ์ด ๋ฉ์น๊ฐ ๋๋ฌด ์ปค์, ์ฑ๊ธ ํดํธ์ ์ํ ๋ด์ง๋ ๊ฐ๋ฐํ๊ฒฝ์์ ์ธ ๋ฐ ์์ด ๋ค ๋ถ๋ฌ์์ผ ํ๋ ๋ฌธ์ ๋ฑ๋ฑ์ด ์กด์ฌํ๋๋ฐ, module federation์ด ๊ทธ๊ฒ์ ํ๋ฅญํ๊ฒ ํด๊ฒฐํด ์ค ์ ์์ ๊ฒ ๊ฐ๋ค. (๋ฌผ๋ก main
์ด ๊ณ ์ฅ๋๋ฒ๋ฆฌ๋ฉด ๋ต์ด ์๊ฒ ์ง๋ง)
์์ฌ์ด ์
๋ฌธ์๊ฐ ์ ๋์์์ผ๋ฉด ์ข์ ๊ฒ ๊ฐ์๋ฐ ์์ง webpack์ ๋ฌธ์๊ฐ ์ข ๋ถ์คํ ๊ฒ ๊ฐ๋ค.
๊ทธ๋์
- https://github.com/webpack/webpack/blob/master/lib/container/ContainerPlugin.js
- https://github.com/webpack/webpack/blob/master/lib/container/ContainerReferencePlugin.js
- https://github.com/webpack/webpack/blob/master/lib/container/ModuleFederationPlugin.js
- https://github.com/module-federation/module-federation-examples
๋ฅผ ๊ทธ๋ฅ ์ฐธ๊ณ ํ๋ฉด์ ํ๋ค. ๋ค๋ฅธ ์ฌํ ๊ธฐ๋ฅ๋ค ์ฒ๋ผ webpack document์์ ์ต์ ์ผ๋ก ๋ค์ด๊ฐ ์ ์๋ object์ ํน์ง์ด๋ ๊ฐ์ ๋ช ์ํด์ฃผ์์ผ๋ฉด ์ข๊ฒ ๋ค.
https://webpack.js.org/concepts/module-federation/#containerplugin-low-level
์์ง์ ๋ฌธ์๊ฐ ๊ทธ๋ฅ ์์ฃผ ๊ฐ๋จํ ์์ ์ ์ปจ์ ์ ๋๋ง ๋ณด์ฌ์ฃผ๊ณ ์์ด์ ์์ฝ๋ค.
์ด๋ฌํ Documentation์ ์์ฌ์ ๋ง๊ณ ๋ ์์ง ์ด๋ ๋คํ ๋จ์ ์ ๋๋ผ์ง ๋ชปํ๋ค. (๋ฌผ๋ก ๋๊ท๋ชจ ์๋น์ค์ ์ง์ ์จ๋ณด์ง๋ ์์์ง๋ง) ํฅ ํ์ create-react-app
์ด๋ผ๋ ์ง, ๋ค๋ฅธ ํ๋ก ํธ์๋ ์ํ๊ณ์์ ์ ๊ทน์ ์ผ๋ก ์ฌ์ฉ๋์ด์ ๋์ฑ ๋ฐ์ ํด๋๊ฐ์ผ๋ฉด ์ข๊ฒ ๋ค.
๋ค์ํ ์์ ๋ค
๋์ฑ ๋ค์ํ ์์ ๋ค์ ์ฌ๊ธฐ์์ ๋ณผ ์ ์๋ค.