Micro App
Let's set up a basic Micro-app, and we are going to create
the mandatory counter example here. Okay, it's a little cheesy, but it does the trick.
npx create-mf-app
? Pick the name of your app: counter
? Project Type: Application
? Port number: 8081
? Framework: react
? Language: javascript
? CSS: CSS
Your 'counter' project is ready to go.
Next steps:
▶️ cd counter
▶️ npm install
▶️ npm start
➜ cd counter
➜ yarn install or npm install
If we take a look what is generated, we see a very basic React app:
counter
├── package.json
├── src
│ ├── App.jsx
│ ├── index.css
│ ├── index.html
│ └── index.js
├── webpack.config.js
└── yarn.lock
Inside the src-folder we are going to create a Counter-component:
(extension can be js(x) orts(x))
counter
├── package.json
├── src
│ ├── App.jsx
│ ├── components
│ │ └── Counter.jsx
│ ├── index.css
│ ├── index.html
│ └── index.js
├── webpack.config.js
└── yarn.lock
This will bootstrap a basic React application, with the only differences being:
- a (more) configurable
webpack.config.jsfile - An
index.jswhich loadsAppdynamically:
import('./App');
Now let's add some code to our Counter component:
import React, { useState } from "react"
export const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<h2>Counter App</h2>
<p>Current count: <strong>{count}</strong></p>
<button onClick={() => setCount(count + 1)}>+
</button>
</div>
)
}
Nothing too fancy here. In any "normal" React-app, you would be
importing this Counter-component somewhere in your app (App.js?)
and fire it off like that. But in a micro-framework, we are going
to use this component as a main entry point for our app.
We can do so by configuring the webpack.config.js file:
const HtmlWebPackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;
module.exports = {
output: {
publicPath: "http://localhost:8081/",
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
},
devServer: {
port: 8081,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.(css|s[ac]ss)$/i,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
plugins: [ // This is important part
new ModuleFederationPlugin({
name: "counter",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Counter": "./src/components/Counter",
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebPackPlugin({
template: "./src/index.html",
}),
],
}
As you can see, there is a lot of stuff in here, but the most
important configuration parts are:
- line 5 & 6
output.publicPathwhich refers to MF entrypoint (or url) - line 42 & 43 the
exposes-object which describes which components are accessible inside this MF - line 40 we publish these functions as
remoteEntry.jsfile on the network
So in our case we tell the outside world they can access our Counter component on url: http://localhost:8081 as remoteEntry.js
Now if we fire up this server, you'll see it will start at port 8081 as expected,
but instead of severing the Counter-component, it starts the default App
component. But that's okay!.
npm run start
# or
yarn start