Skip to main content

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.js file
  • An index.js which loads App dynamically:
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.publicPath which 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.js file 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