Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using federated apps' types in host app #4

Closed
Kjaer opened this issue Feb 17, 2021 · 6 comments
Closed

Using federated apps' types in host app #4

Kjaer opened this issue Feb 17, 2021 · 6 comments

Comments

@Kjaer
Copy link

Kjaer commented Feb 17, 2021

Hey there @gthmb,

I am coming from this discussion → module-federation/module-federation-examples#20

I would like ask how remote apps' types can be included of host apps' scope?

let me illustrate my question with an example


The files below belong to my remote app, meaning I am gonna use this app as dependant into my host app

/* federation.config.json */

{
  "name": "blank",
  "exposes": {
    "./BlankApp": "./src/bootstrap"
  }
}
// webpack.dev.js

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const federationConfig = require('./federation.config.json');
const packageJson = require('../package.json');

module.exports = {
  mode: 'development',
  plugins: [
    new ModuleFederationPlugin({
      ...federationConfig,
      filename: 'blank-app-entry.js',
      shared: packageJson.dependencies,
    }),
  ],
  devServer: {
    port: 17001,
    hot: true,
  },
  devtool: 'eval-source-map',
};

and this my bootstrap file

// src/bootstrap.tsx

import React from 'react';
import ReactDOM from 'react-dom';

import App from './app/App';

const mount = (el: HTMLElement | null): void => {
  ReactDOM.render(<App title="I am an independent app!" />, el);
};

if (process.env.NODE_ENV === 'development') {
  const devRoot: HTMLElement | null = document.querySelector('#blank-app');

  if (devRoot) {
    mount(devRoot);
  }
}

export default mount;

The files below belong to my host app

// webpack.dev.js

const path = require('path');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const packageJson = require('../package.json');

module.exports = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: 'ts-loader',
      }
    ]
  },
  devServer: {
    port: 8090,
    hot: true,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'main',
      remotes: {
        blank: 'blank@http://localhost:17001/blank-app-entry.js',
      },
      shared: packageJson.dependencies,
    }),
  ],
};

and now I want is injecting my remote app inside the host app

//  src/remote-apps/BlankApp.tsx

import React, { useRef, useEffect, ReactElement } from 'react';
import mount from 'blank/BlankApp';

export default function BlankApp(): ReactElement {
  const ref = useRef(null);

  // Pass in our ref and render it once.
  useEffect(() => {
    mount(ref.current);
  });

  return <div ref={ref} />;
}
// src/App.tsx

import React, { FC } from 'react';
import BlankApp from './remote-apps/BlankApp';

interface AppProps {
  title: string;
}

export default function App ({ title }): FC<AppProps> {
  return (
    <main>
       <h1> This is host App </h1>
       <BlankApp />
    </main>
  )
}

When I ran my yarn start command for the host app, I am getting this error:

ERROR in /webpack-module-federation-playground/src/remote-apps/BlankApp.tsx
./remote-apps/BlankApp.tsx 2:18-34
[tsl] ERROR in /webpack-module-federation-playground/src/remote-apps/BlankApp.tsx(2,19)
      TS2307: Cannot find module 'blank/BlankApp' or its corresponding type declarations.
 @ ./src/App.tsx 11:34-68
 @ ./src/index.js 1:0-21

webpack 5.21.2 compiled with 1 error in 9225 ms
ℹ 「wdm」: Failed to compile.

Apperantly My types are available only for my remote app and I am curious how may I move those generated remote apps' types into host app scope and make ts-loader happy.

@gthmb
Copy link
Contributor

gthmb commented Feb 17, 2021

Hi @Kjaer, thanks for posting a question!

Are you using a mono-repo - where your remote and host application packages are in the same project and leverage something like Lerna or Yarn Workspaces to consolidate dependencies across your project?

If so, I think federated-types can help! Once the federation.config.json is in the remote package, you'll want to run the make-federated-types command on that package. The gist of this command is: it creates a typings file for your remote package and updates the type definitions to account for the name of your remote.

For the generated typing file to recognized by the TS compiler, it will need to be in a place the compiler knows to find types. The location that works for us is somewhere in the node_modules/@types directory since TS will look there by default without any configuration required.

federated-types will try to write its output to the project's root node_modeules/@types directory. But you are welcome to pass an outputDir option to specify where it should write to if you need to customize it

outputDir example

scripts: {
    "make-types": "make-federated-types --outputDir ../../my_types/"
}

I hope that helps. Let me know if you need more help.

@Kjaer
Copy link
Author

Kjaer commented Feb 27, 2021

Hey @gthmb
I bring you a real use and my solution so far. But let me answer your questions first:

  • Are you using a mono-repo

Yes I am, but haven't set yarn workspaces or lerna yet.

federated-types comes handy, but I need to tweak it. Here is my way to go:

I am exporting my typings inside a custom @types directory on my remote app, like below

scripts: {
    "make-types": "make-federated-types --outputDir ./@types/remote-app"
}

but federated-types does not create package.json for this process. It's because I am not exporting in the node_modules. For my case I needed this package.json and created manually.

Then on my host app. I add these typings a a dependency on package.json.

devDependencies: {
 "@types/remote-app": "file:../federated-apps/remote-app/@types/remote-app",
}

Then I install the deps for the host app and all my remote-app's typings available on host app.

only path of the typings are hard-coded but I think it's fair trade-off.

What do you think?
Does that sound legit?

@gthmb
Copy link
Contributor

gthmb commented Mar 5, 2021

Hey @Kjaer. That will certainly work. I suspect the reason you are writing your types to your own ./@types directory is that since you aren't using workspaces, each package has its own node_modules directory instead of using a centralized node_modules for the entire repository?

@Kjaer
Copy link
Author

Kjaer commented Mar 6, 2021

That's right! All the packages under the monorepo has its own node_modules. They don't share the dependencies accross the packages.

@Kjaer
Copy link
Author

Kjaer commented Mar 6, 2021

But even though I used the yarn workspaces I would rather using this explicit approach. exporting types default node_modules without tracking it anywhere sounds a bit magic going on under the hood. however exporting types somewhere I can see and install them manually. Making it as explicit as possible. Let the codebase explain itself better.

@gthmb
Copy link
Contributor

gthmb commented Mar 20, 2021

I'm gonna close this issue. Let me know if you think it should be re-opened. Thanks!

@gthmb gthmb closed this as completed Mar 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants