Category Archives: webpack

Micro frontends using React and Module Federation

Module federation allows us to create micro frontends in a pretty simple way.

Start off by running

npx create-mf-app

You’ll be prompted for a few things (these may change in different versions of this app. so these are current settings)

  • Pick the name of your app. This is essentially the name of the shell/host or main application (however you prefer to think of it). So I’m used to Shell application in MAUI, Xamarin Forms, WPF etc. So mine’s going to be named shell.
  • Project Type. We’re going to choose Application (as this is our shell application)
  • Port number. The default is 8080, but I’m going to choose 3000 (as I already have servers running on 8080 for other things).
  • Framework. We’re going to be using react
  • Language. I’m a TypeScript person, so I’m choosing typescript
  • CSS Accept the default which is CSS

Now our shell will be created. So as the prompt will state

  • cd shell
  • npm install
  • npm start

If all worked you’ll be presented by some large text stating the name of the app, framework etc.

Let’s now create another app in exactly the same way, obviously give it a different name (mine’s about), this is one of your micro frontends, also give it a different port, i.e. 8081 if you stuck with the default 8080 for the shell or in my case 3001.

Now go to that application and run it up using the commands below (again)

  • cd about
  • npm install
  • npm start

If all went well we have an application (the shell) running and the about application running as well. So nothing too exciting to see here, but it just goes to show we’re developing “standard” React apps.

What we will now need to do, is create a component that we’re going to expose from this application. All I’m going to do is move the App.tsx code that shows the application name etc. into a component, so if you want to follow along, let’s add a components folder to the src folder and within that add a About.tsx file and moved the code from the App.tsx into it, so it looks like this

import React from "react";

export const About = () => 
(
    <div className="container">
    <div>Name: about</div>
    <div>Framework: react</div>
    <div>Language: TypeScript</div>
    <div>CSS: Empty CSS</div>
  </div>
);

Now the App.tsx looks like this

import { About } from "./components/About";

const App = () => (
  <About />
);

We need to make some changes to webpack.config.js, so locate the ModuleFederationPlugin section.

  • Locate the exposes section and change it to look like this
    exposes: {
      "./About": "./src/components/About"
    },
    

    We expose all the components we want via this section and the shell app can then access them.

  • In your shell/container application we also need to amend the webpack.config.js and locate the remotes section, as you’ve probably worked out, this is going to register the micro frontends to be used within the shell/container. So mine looks like this

    remotes: {
      about: "about@http://localhost:3001/remoteEntry.js"
    },
    

    Let’s see if this work, open the shell/container’s App.tsx and add/change it to look like the below

    import { About } from 'about/About';
    
    const App = () => (
      <div> 
      <div className="container">
        <div>Name: shell</div>
        <div>Framework: react</div>
        <div>Language: TypeScript</div>
        <div>CSS: Empty CSS</div>
      </div>
      <About />
      </div>
    );
    

    Run the about app and then the shell and if all went to plan you’ll see the “About” component in the shell.

    A little more in depth ModuleFederationPlugin

    This is really cool and we can see much of the work is done on the ModuleFederationPlugin, so let’s look a little more in depth into some of the key features

    • name is the name we’re giving to our application
    • library is used to determine how exposed code will be stored/retrieved
    • filename by default is remoteEntry.js, but we can name it whatever we want, for example remoteAbout.js for our about app
    • remotes, as we’ve seen these point to the “modules” we want to include and have the format
      "app-name": "name@remote-host/entryFilename.js"
      
    • exposes, as we’ve seen also the exposes is used to expose components etc. from our micro frontend, it has the format
      "name" : "location-within-app"
      
    • shared is used to share node libraries which the exposed module depends on.

    Code

    Checkout an example shell app with three Material UI/React micro frontend apps at microfrontend-react. Each app. uses Material UI and the shell app. brings them together to create the basics of an application with header, navigation bar and some content.

    Obviously there’s a lot more to do to have a truly interactive micro frontend based application, but it’s a starting point.

Adding webpack-dev-server

In previous posts we’ve used serve as our server, an alternative is the webpack-dev-server. This will give up live reloading and also built-in compilation so that we don’t need to run the build script.

Simply add the relevant package using

yarn add webpack-dev-server -D

Now add the following to your webpack.config.js, first the requires

const path = require('path');

and now the code for the server

module.exports = {
  // ... other config
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 5000

By default “Live reloading” is enabled and hence, now when we make changes to our code and save them, the browser automatically relaods.

We can now just change our “start” script within package.json to

"start": "webpack-dev-server --config config/webpack.config.js"

Modules and webpack

One of the things I wanted to do is allow my React application to “dynamically” load code from another website for an internal application. The idea being other teams can work on their code and the main application will load the latest deployed version if the user navigates to something that uses it – basically we’re talking about writing plugins.

Note: This is not a great idea for any code you cannot 100% control as you’ll obviously have no idea whether it’s malicious, so if you’re thinking of doing something similar make sure you know what code is being downloaded etc.

Where’s my Module gone

To begin with I wrote an “importer” class in a separate application which successfully loaded code – using axios to download it, Typescript to transpile it (if needed) and the standard Module to compile (module._compile) it, then I slotted the code into a Module and away you go. All worked great until it got deployed within the React application and all of a sudden _compile (and other methods) were missing from Module.

The thing is, webpack basically uses it’s own module system.

webpack modules

The post I wrote on getting up and running with webpack was inspired by the module dilemma as I needed to better understand what webpack was doing under the hood.

If we create and build a webpack project we’ll end up with main.js, here’s a snippet from it for the __webpack_require__ function

function __webpack_require__(moduleId) {
   if(installedModules[moduleId]) {
      return installedModules[moduleId].exports;
   }

   var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
   };

   modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
   module.l = true;
   return module.exports;
}

Note: I’ve removed all the comments just to reduce it to the minimal code.

So within the functions web pack generates for our code/exports etc. we’ll see __webpack_require__ used a fair bit, i.e. to load import/require code (import/require is replaced with __webpack_require__) and looking at the function itself we can see that __webpack_require__ uses it’s own module system, i.e. it caches modules in installedModules (which becomes __webpack_require__.c). We can also see that if the module is not cached a new one is created which looks very different from a NodeModule.

Finally, after the module is created a call is made to the new module, which in this case is one of the functions that’s passed to the __webpack_require__ within main.js which basically returns a function to our code which then becomes the “exports” code returned by the __webpack_require__ function.

Up and running with webpack

Don’t read this blog post if you want to learn about webpack, instead go and read the webpack book.

If you’re still reading this post then let’s go ahead and create a minimal project to start learning about webpack.

Creating our project

  • Create a folder for our starter app., mine’s webpack-starter
  • Within the folder run
    yarn init -y
    

    to create our package.json

  • Now install/add webpack and webpack client using
    yarn add webpack webpack-cli
    
  • Create a src folder off of you project’s root – webpack, by default expects a src folder
  • Add a file index.js to the src folder and we’ll include the minimal code, below
    console.log("Hello World");
    
  • To your package.json file add the following
    "scripts": {
       "build": "webpack --mode development"
    }
    

At this point let’s take a look at what we’ve done. We’ve created a minimal package.json and to this added webpack and the webpack-cli. We’ve created a bare bones entry point (index.js) and a build script.

Now if we run the build script using

yarn build

webpack will generate a dist folder off our root folder along with a main.js file, one look at this and you’ll see far more than you probably expected. Webpack’s created a bootstrap along with a lot of module related code. At the bottom of the file is a function that simply executes the eval function which basically evaluates/runs the code that was in the index.js.

Testing our application

I know it’s a rather minimal application, but it’d be nice to see it actually working, so let’s add the relevant piece to the jigsaw

Run the following

yarn add html-webpack-plugin

We’re going to need to tell webpack to use this plugin, so create a new folder off of the root named config within this create a file name webpack.config.js and here’s the contents for this

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: "Webpack starter",
    }),
  ],
};

If you don’t have serve installed then add it using either the command below or add it globally (see React and serve).

yarn add server

Next up we’ll need to rebuild our dist source but before we do that, we placed the webpack.config.js into the config folder, running the build script as it stands will not pickup this file so you can either move it to the root folder or better still update the script to look like this

"scripts": {
   "build": "webpack --mode development --config config/webpack.config.js"
}

Now running yarn build will create, both an index.html file and main.js file within the dist folder. So now we can run the server. Before we do that, let’s add another script task to package.json, so add the following

"start": "serve dist"

Now we can run yarn start which will start a server up (by default it will be http://localhost:5000). Obviously we’re outputting text to the console so you’ll need to switch your browser to dev mode and view the console output.

Webpack output

When you ran yarn build, webpack used the configuration and the HTML plugin within it to generate our index.html file and also supply the title “Webpack starter”. The output from running webpack looked like this (for me at least)

Hash: af14b7dabddfbc78edb6
Version: webpack 4.35.2
Time: 507ms
Built at: 07/02/2019 10:43:49 AM
     Asset       Size  Chunks             Chunk Names
index.html  181 bytes          [emitted]
   main.js    3.8 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.js] 27 bytes {main} [built]
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {0} [built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 2 hidden modules
Done in 3.17s.

As you can see the Child html-webpack-plugin for “index.html”: text tells us what code was generated by the HtmlWebpackPlugin

Along with the creation of the index.html file HtmlWebpackPlugin setup a script tag for calling the webpack generated main.js file.

Before we move on let’s change our index.js file to include an import. So create the folder components off of the src folder and create the file helloComponent.js (code for this is shown below)

export default (text) => {
    const element = document.createElement("div");
    element.innerHTML = text;
    return element;
}

and change the index.js code to the following

import hello from "./components/helloComponent";

document.body.appendChild(hello("Hello World"));

Note: again the above is fundamentally the same as the webpack book. The main difference is that I’m wanting to make this look similar to how I’ve learned to use/set-up React as it helps give a feel for how one might transition between React and lower level webpack designed projects or vice versa.

Now run yarn build followed by yarn start. If all goes well you’ll see some interesting changes to the main.js file and ofcourse you should now see actual HTML elements in your browser. I’m far more interested in the main.js changes than the fact we have any output.

More on main.js

If you compare the original main.js file with the new one, you’ll notice that it’s fundamentally the same, it has the webpackBootstrap section as well as all the code setting up the __webpack_require__ properties. Ultimately though the original main.js came down to this line

(function(module, exports) {
eval("console.log(\"Hello World\");\n\n//# sourceURL=webpack:///./src/index.js?");
})

and we can clean this up further just turning it into

(function(module, exports) {
eval("console.log(\"Hello World\");");
})

With regards to the second implementation, although the bulk of the file is still made up with the webpackBootstrap code etc. now we have the following code combined into the single main.js file (I’ve removed comment blocks etc. to clean the code up somewhat)

(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = 
((text) => {\r\n    const element = document.createElement(\"div\");\r\n    element.innerHTML = text;\r\n    return 
element;\r\n});\n\n//# sourceURL=webpack:///./src/components/helloComponent.js?");
}),

/*! no exports provided */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var 
_components_helloComponent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./components/helloComponent */ \"./src/components/helloComponent.js\");\n\r\n\r\ndocument.body.appendChild(Object(_components_helloComponent__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"Hello World\"));\n\n//# sourceURL=webpack:///./src/index.js?");
})

The first function is our helloComponent.js file wrapped in a function, notice how we no longer simply eval the JavaScript but instead eval __webpack_require__. The second function is our index.js.

There’s a lot going on, especially with all the commented code and new lines etc. so let’s clean it up, remove the eval function and format it and then we see that this is the index.js code in a function

__webpack_require__.r(__webpack_exports__);
var _components_helloComponent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(\"./src/components/helloComponent.js\");
document.body.appendChild(Object(_components_helloComponent__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(\"Hello World\"));

The function that this code is within is passed a module, __webpack_exports__ and __webpack_require__, so firstly we call the function __webpack_require__.r with the __webpack_exports__ and this defines the property for either Module or __esModule.

Next we declare a variable named after the folder and filename (i.e. components/helloComponent changes to _components_helloComponent) which in turn is suffixed with the __WEBPACK_IMPORTED_MODULE_0__ (the number 0 increments for each addition module included). This variable is assigned the resultant module after passing the string “./src/components/helloComponent.js” to __webpack_require__. The string is simply a module id, hence as it’s based upon the file system will be unique to the project containing it.

The final line is a call to the document.body.appendChild (as per our index.js) but the function hello (exported from helloComponent) has been replaced by the _components_helloComponent__WEBPACK_IMPORTED_MODULE_0__ variable indexed by the default export.

Basically what’s happened is webpack has parsed our code, replacing the imports with functions which wrap the code from the imports. It’s created it’s own module system using __webpack_require__

What on earth are all the __webpack_require__ properties doing?

What’s happening is that the first function function(modules) (the webpackBootstrap function) is called, next the __webpack_require__ object is setup, with modules and installed modules being assigned to __webpack_require__.m, em>__webpack_require__.c and the rest of the properties setup with functions and so on.

Here’s a list of the current _webpack_require__ properties and their usage (taken from the webpack source)

  • __webpack_require__.s = the module id of the entry point
  • __webpack_require__.c = the module cache
  • __webpack_require__.m = the module functions
  • __webpack_require__.p = the bundle public path
  • __webpack_require__.i = the identity function used for harmony imports
  • __webpack_require__.e = the chunk ensure function
  • __webpack_require__.d = the exported property define getter function
  • __webpack_require__.o = Object.prototype.hasOwnProperty.call
  • __webpack_require__.r = define compatibility on export
  • __webpack_require__.t = create a fake namespace object
  • __webpack_require__.n = compatibility get default export
  • __webpack_require__.h = the webpack hash
  • __webpack_require__.w = an object containing all installed WebAssembly.Instance export objects keyed by module id
  • __webpack_require__.oe = the uncaught error handler for the webpack runtime
  • __webpack_require__.nc = the script nonce

Webpack Loaders

I’m going to end this post with a very quick look at webpack loaders. Whilst there’s nothing wrong with using plain JavaScript, I come from a statically typed world and rather like using TypeScript to catch those stupid mistakes. So let’s set-up webpack to work with TypeScript.

Webpack comes with the concept of loaders, these are basically file processors that become part of the webpack pipeline. For example, they might generate code from configuration files or transpile from other languages etc. Everything then gets bundled via the webpack pipeline.

To add typescript capabilities to webpack we need to edit the webpack.config.js, adding the following

module: {
   rules: [
      {
         test: /\.tsx?$/,
         use: 'ts-loader',
         exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: [ '.tsx', '.ts', '.js' ]
  }

We’ll also need to run

yarn add typescript ts-loader

To installed typescript (if not already installed) and ts-loader, which is used within webpack. Assuming you’re going to now change all .js files (including index.js) to .ts files you’ll also need to amend the webpack.config.js entry to this

entry: './src/index.ts',

Now run your build script as normal and assuming your code is valid TypeScript then you should successfully created a webpack bundle based upon your TypeScript code.

I think that about covers getting webpack up and running, but I’m sure there’ll be more posts on the topic as I delve deeper.