module-federation-examples icon indicating copy to clipboard operation
module-federation-examples copied to clipboard

How typesafe can a remote be with Typescript?

Open echarles opened this issue 5 years ago • 71 comments

I can see a disadvantage using remotes libraries vs the classical libraries where e.g. typescript can be used to enforce strong types. The current typescript example defines a generic app2/Button which is similar defining a any type.

https://github.com/module-federation/module-federation-examples/blob/cf2d070c7bbb0d5f447cfe33f69e07bcf85f8ddb/typescript/app1/src/app2.d.ts#L3-L7

As the remote var does not have type, I don't see a way to benefit from the app2 types in app1. Any idea on this?

echarles avatar May 04 '20 17:05 echarles

@ScriptedAlchemy

echarles avatar May 04 '20 19:05 echarles

I believe, in our case, we should be able to be fine with not having type checking for remote libraries. We can use shared libraries for extension authors import core libraries, so they don't need to be remote and dynamically imported.

saulshanabrook avatar May 05 '20 11:05 saulshanabrook

I have tried a hacky way to benefit from type-safety importing the definition of the remote project (app2, here widgets) in the host project (app1). As shown on the below image, lazy returns the correct type and non acceptable props are underlined in red. I guess a solution would be to publish the types separately and a 3rd party could depend on those types.

Screenshot 2020-05-05 at 17 51 48

echarles avatar May 05 '20 15:05 echarles

Or have a monorepo of types

ScriptedAlchemy avatar May 07 '20 18:05 ScriptedAlchemy

What I have done is use node to generate a .d.ts file, that are loaded by our global.d.ts file.

So each Remote outputs a .d.ts file that go declare module "dlaWidgets/Widets" {}, with using the ts.createProgram you can tap into the typeChecker stuff that i use to print its public api. Kinda like getting tsc to ouput definition files, but instead spit them into a single file.

Then each of my hosts import this .d.ts file, which I have registered in a global.d.ts, that all of my projectReferenced monorepo apps include.

So there is a small disconnect between a webpack config, and the types. But any exposed things, are automagically available with types in my entire app. So ts might say its a valid import, but just need to actially configure it with webpack also.

Working on open sourcing this generative thing - just gotta make sure it works in all use cases.

maraisr avatar May 08 '20 02:05 maraisr

@maraisr your approach sounds very interesting. Keep us posted if you open source it.

echarles avatar May 13 '20 07:05 echarles

ill raise this with Microsoft - they will have to support federation since they are going to be using it heavily

ScriptedAlchemy avatar May 20 '20 12:05 ScriptedAlchemy

ill raise this with Microsoft - they will have to support federation since they are going to be using it heavily

Awesome!

echarles avatar May 20 '20 12:05 echarles

mono repo is not recomended when our app is very large,independent repo is better.

💡We can watch and output independent repo's d.ts, then start a server which can make d.ts could be downloaded. When other apps need d.ts,just download it to your project from local server.

zhangwilling avatar May 21 '20 07:05 zhangwilling

same issue and suggest like deno remote url type.d.ts

ckken avatar May 31 '20 10:05 ckken

I found some devs solving this by modifying TS loader. Specifically for module Federation

ScriptedAlchemy avatar May 31 '20 12:05 ScriptedAlchemy

how to fix it

ckken avatar Jun 10 '20 14:06 ckken

i think it should be solved by IDE, such as vscode.

zhangwilling avatar Jun 11 '20 02:06 zhangwilling

yeah IDE can solve it, assuming it supports that. Meeting with Microsoft in august - seeking changes to TS to support MF, in doing so IDEs will have to follow the spec

ScriptedAlchemy avatar Jun 11 '20 23:06 ScriptedAlchemy

This remains a solution

What I have done is use node to generate a .d.ts file, that are loaded by our global.d.ts file.

So each Remote outputs a .d.ts file that go declare module "dlaWidgets/Widets" {}, with using the ts.createProgram you can tap into the typeChecker stuff that i use to print its public api. Kinda like getting tsc to ouput definition files, but instead spit them into a single file.

Then each of my hosts import this .d.ts file, which I have registered in a global.d.ts, that all of my projectReferenced monorepo apps include.

So there is a small disconnect between a webpack config, and the types. But any exposed things, are automagically available with types in my entire app. So ts might say its a valid import, but just need to actially configure it with webpack also.

Working on open sourcing this generative thing - just gotta make sure it works in all use cases.

ScriptedAlchemy avatar Jun 11 '20 23:06 ScriptedAlchemy

@zhangwilling it's not a part of IDE, it's all about typechenking of typescript compiler itself

glebmachine avatar Jun 14 '20 14:06 glebmachine

I like the IDE idea @zhangwilling, but that will be more about aiding the development flow. However... I think this should still remain in the compiler itself, or a definition file that the compiler reads in. Personally feel its the latter.

If you solve to get the compiler to be aware of the remotes, then you don't have to worry about IDE, and specific IDE support as all IDE's will get this knowledge for free.

My solution outlined above still holds true. Its not terribly bad to implement. I'm hoping to get something open sourced soon.

Just wanting to get an idea from the community:

  1. Are you using a monorepo?
  2. Are your exposes objects exposed as standard javascript/typescript files? (Rather than inline in your webpack.config.js)
  3. Are the request's standard node-esk requests, ie no @svgr/webpack!./icon.svg, where the request would firsts require a loader?

I've taken some assumptions to how I build software, but just wanting to get more scope so that this tool when released would solve issues for the majority.

maraisr avatar Jun 14 '20 23:06 maraisr

@zhangwilling it's not a part of IDE, it's all about typechenking of typescript compiler itself

@glebmachine yes, it is not part of IDE. Maybe TS can provide extra typing file config, but IDE plugin can do this, it could be improved much by IDE. Same as maven in IDEA, maven is not part of IDEA, but it does. Because it's a very important infrastrure.👻

zhangwilling avatar Jun 15 '20 01:06 zhangwilling

I like the IDE idea @zhangwilling, but that will be more about aiding the development flow. However... I think this should still remain in the compiler itself, or a definition file that the compiler reads in. Personally feel its the latter.

If you solve to get the compiler to be aware of the remotes, then you don't have to worry about IDE, and specific IDE support as all IDE's will get this knowledge for free.

My solution outlined above still holds true. Its not terribly bad to implement. I'm hoping to get something open sourced soon.

Just wanting to get an idea from the community:

  1. Are you using a monorepo?
  2. Are your exposes objects exposed as standard javascript/typescript files? (Rather than inline in your webpack.config.js)
  3. Are the request's standard node-esk requests, ie no @svgr/webpack!./icon.svg, where the request would firsts require a loader?

I've taken some assumptions to how I build software, but just wanting to get more scope so that this tool when released would solve issues for the majority.

@maraisr monorepo is not recommend when facing large project. so a large project would be seprated to many litttle repos by domain or module. But, we usually would put them together into same parent directory or we use worksapce in VSCode to oragnize them together logically 😈.

zhangwilling avatar Jun 15 '20 02:06 zhangwilling

@zhangwilling yeah

But, typescript are trying to resolve imports. And fails when facing federation module import declaration. It looks like we have to be able to declare special federation modules right into tsconfig.json file or similar way.

For now, i'm workaround this with by loads file by document.createElement('script').

glebmachine avatar Jun 15 '20 11:06 glebmachine

Depending on your use case/setup you could solve this by having a tsconfig.json in the project root and leverage path-mapping to resolve the apps entry points (Remote Name + Exposed module):

/tsconfig.json:

...
"paths": {
      "app1/Remote": ["./packages/app1/src/app"],
      "app2/Remote": ["./packages/app2/src/app"]
}
...

/packages/app1/tsconfig.json & /packages/app2/tsconfig.json:

{
  "extends": "../../tsconfig.json"
}

I've setup a little prototype using this setup: https://github.com/rangle/federated-modules-typescript

micmro avatar Jun 16 '20 18:06 micmro

You got to be careful with that approach though. It does mean that you cannot also use path mappings for other things in your app, because if you are you're probably also using tsconfig paths webpack resolver, in which case module federation plugin can't "remote" those. You can but you'd have to have a special build-time tsconfig that excludes the MF mappings.

maraisr avatar Jun 17 '20 01:06 maraisr

Thanks @maraisr , that is a very good point I had not considered. I suppose then it is a trade-off of manual remote interface vs potential manual MF path-mapping overwrites.

micmro avatar Jun 17 '20 01:06 micmro

1.webpack provide a template for the "d.ts" module building 2.webpack config decare the use of remote "d.ts" module 3.IDE support using static inspection for the second point

doerme avatar Jun 29 '20 07:06 doerme

fix it with npm-dts create in dist & request by app

ckken avatar Jul 01 '20 14:07 ckken

What I have done is use node to generate a .d.ts file, that are loaded by our global.d.ts file.

So each Remote outputs a .d.ts file that go declare module "dlaWidgets/Widets" {}, with using the ts.createProgram you can tap into the typeChecker stuff that i use to print its public api. Kinda like getting tsc to ouput definition files, but instead spit them into a single file.

Then each of my hosts import this .d.ts file, which I have registered in a global.d.ts, that all of my projectReferenced monorepo apps include.

So there is a small disconnect between a webpack config, and the types. But any exposed things, are automagically available with types in my entire app. So ts might say its a valid import, but just need to actially configure it with webpack also.

Working on open sourcing this generative thing - just gotta make sure it works in all use cases.

In addition to this approach, what about packaging that d.ts ouput file and deploying it as a tar.gz alongside your webpack artifacts? Similar to a @types/* package except not published to registry. Then a host app can optionally add dev-dependency referencing the remote tar.gz file and always have latest TS types.

I think this approach would work well for non-monorepo cases.

mizx avatar Oct 05 '20 17:10 mizx

I would love some feedback on how I am currently solving this issue:

I have a mono-repo with several packages. Each package defines a federation.config.json file that declares its remote name and what it exposes, like so:

packages/app2/config/federation.config.json

{
    "name": "app2",
    "filename": "remoteEntry.js",
    "exposes": {
        "./Button": "./app2/Button"
    }
}

The webpack config file for the module imports this config and spreads it into the plugin config like:

packages/app2/config/webpack.config.js

plugins: [
    new ModuleFederationPlugin({
        ...federationConfig,
        shared: {
            ...deps,
        },
    }),
],

I then have an NPM module I am calling federated_types which exposes a CLI command called make-federated-types which can be called via an npm script in a package.

This command finds and reads the package's federation.config.json and uses the TS compiler to generate types with a similar process to what @maraisr described - however I write the types into an @types/__federated_types directory under the projects node_modules directory. This is nice because there is no extra resolution configuration needed in the tsconfig file or webpack config files since they look in @types as a default location.

The command does allow for specifying where the federated typing files should be written, but by default, it writes to the node_modules/@types/__federated_types directory.

Then in each package.json, I added an entry to the scripts node, like:

  "scripts": {
        "make-types": "make-federated-types"
    },

And since I'm using Lerna, when I run lerna run make-types it generates the typings for the items I have exposed via federation

Does this seem like a sound approach? Is writing to the node_modules directory a bad idea? Is there a better location to write to?

gthmb avatar Nov 02 '20 03:11 gthmb

As long as node resolves properly to the location you write to, it's a sound approach. I think haha.

What one could do is create a GitHub crawler that pulls other repos TS def files remotely (similar to webpack code streaming) and write them on demand. You'd only need a list of repos / remotes to "look" for JS defs to pull ahead of time.

Basically your mono repo idea. Applied to polyrepo and using the network

If anyone wants to try building this- I'll give you maintainer access to a repo under the official MF organization

ScriptedAlchemy avatar Nov 02 '20 05:11 ScriptedAlchemy

Thanks for the feedback!

For your suggestions around a poly-repo solution, are you thinking an application would have a config file that defined the repos/remotes they are using, and then a script would pull those repos and generate local copies of the types needed?

Just trying to get a clearer picture. I would be happy to work on something like that. Happy and eager to help how I can!

gthmb avatar Nov 02 '20 18:11 gthmb

Yeah basically. Imagine you listed an array of objects that contain info about your remotes.

Then we could search git repo via api or just the TS defs related to remote files and write those strings to disk in your types directory

Basically replaced fs with fetch and we get the raw git text from api

ScriptedAlchemy avatar Nov 02 '20 18:11 ScriptedAlchemy

This is easier to instrument with federation dashboard because it know what hosts uses what remotes and where they are. In the future I could tack something into dashboard so one could query dashboard for the defs - then write the strings.

ScriptedAlchemy avatar Nov 02 '20 18:11 ScriptedAlchemy

Very cool. I am happy to help!

If it's useful to anyone looking at sharing type definitions between their packages in a mono-repo, here is what I am using: https://github.com/pixability/federated-types

gthmb avatar Nov 02 '20 20:11 gthmb

Will check this out and see what's needed to make this work for both poly and monorepo

ScriptedAlchemy avatar Nov 03 '20 23:11 ScriptedAlchemy

Here is a better solution with Typescript. Form a closed loop of types, from generation to reference complete for Module Federation project. https://github.com/efoxTeam/emp/tree/main/packages/emp-tune-dts-plugin

flyyuan avatar Nov 04 '20 03:11 flyyuan

Cool, I'll check that out too. Thanks!

gthmb avatar Nov 19 '20 22:11 gthmb

Has anyone got a reliable solution for projects that don't use a monorepo?

rickihastings avatar Jan 14 '21 16:01 rickihastings

Has anyone got a reliable solution for projects that don't use a monorepo?

https://github.com/efoxTeam/emp/tree/main/packages/emp-tune-dts-plugin

ckken avatar Jan 14 '21 16:01 ckken

Is anyone having issues with eslint when importing a federated module?

image image

:point_down: eslint config

{
  "env": {
    "browser": true,
    "jest": true
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "extends": [
    "airbnb",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:jest/recommended",
    "prettier/@typescript-eslint",
    "plugin:prettier/recommended"
  ],
  "plugins": ["react", "react-hooks", "@typescript-eslint", "jest", "prettier"],
  "rules": {
    "import/extensions": "off",
    "import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn",
    "react/prop-types": "off",
    "react/react-in-jsx-scope": "off",
    "react/jsx-uses-react": "off",
    "react/jsx-filename-extension": [1, { "extensions": [".ts", ".tsx"] }],
    "react/jsx-one-expression-per-line": "off",
    "import/prefer-default-export": "off",
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "prettier/prettier": "error"
  },
  "settings": {
    "import/resolver": {
      "typescript": {}
    }
  }
}

:point_down: tsconfig

{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "lib": ["dom", "dom.iterable", "esnext"],
    "moduleResolution": "node",
    "noEmit": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "baseUrl": "./src",
    "paths": {
      "routes/*": ["routes/*"],
      "services/*": ["services/*"],
      "pages/*": ["pages/*"],
      "components/*": ["components/*"]
    }
  },
  "include": ["src"]
}

Thanks! :pray:

lm2almeida avatar Feb 23 '21 13:02 lm2almeida

@lm2almeida I think the issue is that your TypeScript project doesn't understand what remote is, and therefore eslint doesn't understand it as your eslint is configured to use TypeScript as the import resolver.

As for a solution to the TypeScript issue. The only solutions I've found so far are in this thread and they involve building the types separately and then importing them from somewhere, the main issues I'm seeing with this are the manual steps of building the types, and then syncing up these types across your projects when the files change.

At the moment it's an unsolved problem imo. It's less of an issue in a monorepo though.

rickihastings avatar Feb 23 '21 15:02 rickihastings

@lm2almeida I think the issue is that your TypeScript project doesn't understand what remote is, and therefore eslint doesn't understand it as your eslint is configured to use TypeScript as the import resolver.

As for a solution to the TypeScript issue. The only solutions I've found so far are in this thread and they involve building the types separately and then importing them from somewhere, the main issues I'm seeing with this are the manual steps of building the types, and then syncing up these types across your projects when the files change.

At the moment it's an unsolved problem imo. It's less of an issue in a monorepo though.

Thanks for the answer, @rickihastings. The problem here is that even with the declared typing the error still occurs. Looking forward for a solution :pray:

image

lm2almeida avatar Feb 23 '21 16:02 lm2almeida

Whoever stuck on typings here is my approach.

First of all, I am relying on @gthmb 's federated-types package: https://github.com/module-federation/module-federation-examples/issues/20#issuecomment-720719885

on my remote app, I created federation-config.json

/* federation.config.json */

{
  "name": "blank",
  "exposes": {
    "./RemoteApp": "./src/bootstrap"
  }
}

then, I exporting my typings in a custom @types directory on my root. (remote app)

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

federated-types export types inside node_modules as default behaviour. For that reason it does not create package.json if custom output directory set. However, I need the package.json because I am adding my remote app's type as a dependency to my host application.

after running make-types command, it generates the types by looking at my federation-config.json and I manually created package.json. In the end, you should have something similar:

├── @types
│   ├── remote-app
│   │   ├── index.d.ts
│   │   ├── remote-app.d.ts
│   │   ├── package.json

I have my typings ready to be import as a dependency for my host app. On my host app's package.json I added these typings as devDependency

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

then run the yarn install as usual, and all my remote-app's typings are available for the host app, without a hassle. What's good at this, all these typings creations and importing to host app is automated and less error-prone.

Kjaer avatar Feb 27 '21 19:02 Kjaer

I was able to get my project with module federation and typescript working, along with typesafety of remote components, with minimal cost:

  • Each remote application has a declaration.d.ts file in the root defining the props of exposed components:
declare module "app2/App" {
    type Props = import('./src/components/App/App').Props;

    const App: React.FunctionComponent<Props>;

    export default App;
}
  • Parent application has the following entry in tsconfig.json:
{
    "include": [
        "../*/*.d.ts"
    ]
}

in this scenario the lazy-loaded components are still reporting missing/incorrect props passed. The only con I can see here is that you need to make sure every newly exposed component is added to the .d.ts file, but that's a minimal cost. Also, make sure you've set "module": "esnext" in compilerOptions in your tsconfig.json - setting "commonjs" will result in console error reg. shared module not being available.

EDIT: One more (cosmetic) issue is, that VSCode's built-in TS server doesn't seem to recognize the "include" part of tsconfig.json resulting in marking the dynamic import in parent app as error (Cannot find module 'app2/App' or its corresponding type declarations) but the build works fine. If anyone knows a fix for this, I'd be ever so grateful.

grzegorzjudas avatar Apr 22 '21 11:04 grzegorzjudas

Sharing a recent setup in my current company that is NOT monorepo (can't share code as it's not open-sourced, but happy to clarify!).

The key components are:

  1. npm org (to publish the exposes type, not the code)
  2. (Optional) A custom pipeline (like CRA) that abstract the webpack config so I can enforce similar conventions for all projects.

The convention that I enforce is that modules that are exposed must be in a src/exposes folder. Then, I programmatically set the exposes key of module federation plugin based on the file, e.g.

return new ModuleFederationPlugin({
  exposes: {
    './exposes/file1': './src/exposes/file1'
  }
})

I have a custom tsconfig.json file that only includes src/exposes folder. When it compiles, it will generate another folder like

type
/exposes
  /file1.d.ts
  /file2.d.ts
/components
  /...

Writing some custom code, I generate a package.json file in that folder with name @npmorg/<appName>, which can be published.

In the host application, when it import the remotes, always use the convention @npmorg/<appName>/exposes/<module> and also install @npmorg/<appName> package (which only consists of type definition).

Everytime the remote app change its interface in exposes file, it just need to regenerate the type definition folder and publish it and the host just need to update that package to get the latest type definition.

malcolm-kee avatar May 21 '21 17:05 malcolm-kee

Sharing my setup:

For the host which exposes modules

  1. I created a dts-loader which will emit and collect the .d.ts file. And also it generates the entry .d.ts file based on the exposes.
  2. Then I create a tarball(.tgz file) for the emitted types & entries. And I deployed this tarball with the application's statics, for example, to http://localhost:9000/app-dts.tgz

For the host which requires remotes

  1. I created a webpack plugin WebpackRemoteTypesPlugin which will download and unpack the tarball from remote automatically when running webpack.

More details can be found here: https://github.com/ruanyl/dts-loader Comments & feedbacks are welcome :)

ruanyl avatar May 25 '21 08:05 ruanyl

For those of you creating .d.ts files, do you also have enums? I tried that approach earlier this year and found that my runtimes would throw up because enums need to be compiled to (extraordinarily weird) JS. Maybe I was doing something wrong?

adrianbw avatar Jun 07 '21 23:06 adrianbw

I have a file in my remote which is exporting enums and interfaces and i need to consume this in my host application. My build keeps failing as either it fails with the error: / not declared and when i declare it, it fails with the error: not being used and at the place where i am using it it throws the error: cannot use namespace.

arshita04 avatar Jun 24 '21 19:06 arshita04

Sharing my setup:

For the host which exposes modules

  1. I created a dts-loader which will emit and collect the .d.ts file. And also it generates the entry .d.ts file based on the exposes.
  2. Then I create a tarball(.tgz file) for the emitted types & entries. And I deployed this tarball with the application's statics, for example, to http://localhost:9000/app-dts.tgz

For the host which requires remotes

  1. I created a webpack plugin WebpackRemoteTypesPlugin which will download and unpack the tarball from remote automatically when running webpack.

More details can be found here: https://github.com/ruanyl/dts-loader Comments & feedbacks are welcome :)

Does this solution work well with Polyrepo as well?

alexis-regnaud avatar Sep 03 '21 15:09 alexis-regnaud

More then 1 year from the day that issue was open , do we have some good solution how we can resolve that issue and make typescript work well with the plugin ? Tnx

@ScriptedAlchemy

OriAmir avatar Oct 13 '21 22:10 OriAmir

I'm not a TS user so I'm no help here.

ScriptedAlchemy avatar Oct 30 '21 01:10 ScriptedAlchemy

Sharing my setup:

For the host which exposes modules

  1. I created a dts-loader which will emit and collect the .d.ts file. And also it generates the entry .d.ts file based on the exposes.
  2. Then I create a tarball(.tgz file) for the emitted types & entries. And I deployed this tarball with the application's statics, for example, to http://localhost:9000/app-dts.tgz

For the host which requires remotes

  1. I created a webpack plugin WebpackRemoteTypesPlugin which will download and unpack the tarball from remote automatically when running webpack.

More details can be found here: https://github.com/ruanyl/dts-loader Comments & feedbacks are welcome :)

I was able to get this working in a polyrepo. One thing that tripped me up pretty hard was your exposes must explicitly reference the files you're exposing.

  exposes: {
    './theme': './src/themes/index.ts',
    './helpers': './src/helpers/index.ts',
    './constants': './src/constants/index.ts',
  }

This will not work:

  exposes: {
    './theme': './src/themes,
    './helpers': './src/helpers',
    './constants': './src/constants,
  }

Opened up this issue so hopefully it can be improved: https://github.com/ruanyl/dts-loader/issues/7

Also, I was able to automate the archiving by using tar-webpack-plugin, so my types are generated and synced whenever I npm start - pretty neat!

dgpt avatar Nov 05 '21 18:11 dgpt

federation.config.json

"filename" is not used by federated-types. So "filename" should stay in webpack.config

osya avatar Nov 30 '21 12:11 osya

I'm not a TS user so I'm no help here.

Who can discuss on this theme? Maybe RFC or something else? 2y opened issue with common case where answer is "I'm not a TS" look weird

popuguytheparrot avatar Dec 11 '21 16:12 popuguytheparrot

I disagree. In the end, Typescript support must not an official thing for this tech. On the other hand, there are plenty of solutions mentioned above works just fine.

Kjaer avatar Dec 11 '21 16:12 Kjaer

I do think a good Typescript solution is very important for the long-term success of module federation, given how TS keeps growing in popularity.

Would something like this work?

Package A: The code you want to get through module federation. Package B: A package that imports Package A through module federation and then re-exports those functions with correct typing. Package C: Your application, which adds Package B through npm.

Package A and Package B should be published from the same repo, so that you don't have to keep anything in sync between repos. I'm honestly not sure if this method would preserve the benefits of module federation—what happens when webpack runs on package C?

adrianbw avatar Dec 18 '21 17:12 adrianbw

emp v2 work very very use remote d.ts

ckken avatar Dec 20 '21 06:12 ckken

Sharing my setup:

For the host which exposes modules

  1. I created a dts-loader which will emit and collect the .d.ts file. And also it generates the entry .d.ts file based on the exposes.
  2. Then I create a tarball(.tgz file) for the emitted types & entries. And I deployed this tarball with the application's statics, for example, to http://localhost:9000/app-dts.tgz

For the host which requires remotes

  1. I created a webpack plugin WebpackRemoteTypesPlugin which will download and unpack the tarball from remote automatically when running webpack.

More details can be found here: https://github.com/ruanyl/dts-loader Comments & feedbacks are welcome :)

I was able to get this working in a polyrepo. One thing that tripped me up pretty hard was your exposes must explicitly reference the files you're exposing.

  exposes: {
    './theme': './src/themes/index.ts',
    './helpers': './src/helpers/index.ts',
    './constants': './src/constants/index.ts',
  }

This will not work:

  exposes: {
    './theme': './src/themes,
    './helpers': './src/helpers',
    './constants': './src/constants,
  }

Opened up this issue so hopefully it can be improved: ruanyl/dts-loader#7

Also, I was able to automate the archiving by using tar-webpack-plugin, so my types are generated and synced whenever I npm start - pretty neat!

Could you please share how you got tar-webpack-plugin to work?

sabeekaq avatar Dec 23 '21 13:12 sabeekaq

Although I wonder what should be done when running tsc in CI, I think you can do the same thing with filemanager-webpack-plugin like this without using tar-webpack-plugin.

    new FileManagerPlugin({
      events: {
        onEnd: {
          archive: [
            {
              source: '.wp_federation',
              destination: 'path/to/types.tar.gz',
              format: 'tar',
            }
          ],
        },
      },
    }),

makotot avatar Dec 31 '21 02:12 makotot

Depending on your use case/setup you could solve this by having a tsconfig.json in the project root and leverage path-mapping to resolve the apps entry points (Remote Name + Exposed module):

/tsconfig.json:

...
"paths": {
      "app1/Remote": ["./packages/app1/src/app"],
      "app2/Remote": ["./packages/app2/src/app"]
}
...

/packages/app1/tsconfig.json & /packages/app2/tsconfig.json:

{
  "extends": "../../tsconfig.json"
}

I've setup a little prototype using this setup: rangle/federated-modules-typescript

This is the easier and quickest solution that worked for me! Thanks @micmro 🙏🏻

simonepizzamiglio avatar Jan 12 '22 10:01 simonepizzamiglio

If anyone is looking for a poly-repo solution, I've put together this sample app with some instructions to share the type definitions of exposed files: https://github.com/jrandeniya/federated-types-sample

It's relatively straight forward using the great work done by @touk/federated-types and webpack-remote-types-plugin

jrandeniya avatar Feb 22 '22 18:02 jrandeniya

I've also been using the tsconfig.json paths solution and it has been working well, but it's not without issues. That is, those remotes that you've configured with paths will still need to be fully installed when you check types, or you'll end up with errors:

./packages/app1/src/app/useSelectedEntity.ts:2:21 - error TS2307: Cannot find module 'lodash/isEqual' or its corresponding type declarations.

2 import isEqual from "lodash/isEqual";
                      ~~~~~~~~~~~~~~~~

stefan-toubia avatar Mar 30 '22 23:03 stefan-toubia

We have released a Federated Types Plugin beta.

Theres some gaps in the type def bundling, we are aware of it and working on improvements.

https://app.privjs.com/buy/packageDetail?pkg=@module-federation/typescript

ScriptedAlchemy avatar May 09 '22 05:05 ScriptedAlchemy

We have released a Federated Types Plugin beta.

Theres some gaps in the type def bundling, we are aware of it and working on improvements.

https://app.privjs.com/buy/packageDetail?pkg=@module-federation/typescript

It looks promising. Is there a plan to open source it in regular npm ?

malopgrics avatar May 09 '22 12:05 malopgrics

We have released a Federated Types Plugin beta.

Theres some gaps in the type def bundling, we are aware of it and working on improvements.

https://app.privjs.com/buy/packageDetail?pkg=@module-federation/typescript

It looks promising. Is there a plan to open source it in regular npm ?

Not currently, I generally only publish OSS to npm. Closed source, even if it's free, usually stays on priv

Perhaps in the future depending on data & feedback during beta & RC phases

ScriptedAlchemy avatar May 27 '22 06:05 ScriptedAlchemy

Hey @ScriptedAlchemy, I'm glad there's finally a solution emerging to share Typescript definitions among microfrontends from the author, despite of saying that I'm not a TS user so I'm no help here.!

I was trying other solutions like @touk/federated-types that reads a federation.config.json and generates types in node_modules@types__federated_types

It does that for local modules but does not collect types for modules that are exposed from node_modules.

Our federation.config.json config looks like this:

{
  "name": "commonLibs",
  "exposes": {
    "./AxiosHttp": "./src/api-services/axios-client/index",
    ...
    "./@cloudbeds/ui-library": "@cloudbeds/ui-library",
    "./react": "react",
    "./react-dom": "react-dom",
    "./react-query": "react-query",
    "./react-router-dom": "react-router-dom"
  }
}

Everything after ... is from node_modules and we want that to be part of commonLibs microfrontend so that will consume a single version of these without even installing react in consuming app's package.json, e.g.

import react from 'commonLibs/react';

Coupled with webpack-remote-types-plugin we could have updated types without uploading them to S3 or publishing to NPM. Unfortunately that doesn’t work with Dynamic remotes - it needs an actual URL from which the remote is served from. We use external-remotes-plugin and uses a dynamic variable name, e.g. [window.commonLibs] instead of localhost:4242

Here's a screen of the error we are experiencing:

image

steven-pribilinskiy avatar Jun 12 '22 10:06 steven-pribilinskiy

Now we are trying to achieve that with Federated Types Plugin beta.

It would be really great if the closed source plugin would have an official place to open issues. Probably this thread is currently serves this purpose

steven-pribilinskiy avatar Jun 12 '22 10:06 steven-pribilinskiy

@module-federation/typescript creates types in dist/@mf-typescript

The __types_index.json file contains list of type definition files for all exposed modules, here it's:

[
  "index.d.ts",
  "index.d.ts",
  "index.d.ts",
  "index.d.ts",
  "ui-library.d.ts",
  "react.d.ts",
  "react-dom.d.ts",
  "react-query.d.ts",
  "react-router-dom.d.ts"
]

Although it lists packages in node_modules, e.g. react.d.ts and ui-library.d.ts they are not included in dist/@mf-typescript

Also it would be great if that file's purpose would be documented here

steven-pribilinskiy avatar Jun 12 '22 10:06 steven-pribilinskiy

Compared to @touk/federated-types, instead of a single file with all types, @module-federation/typescript recreates the directory structure which is neat.

There's one serious issue with @module-federation/typescript plugin is that the types cannot be used for microfrontends that are served from production environment (unless we don't know something). E.g. imagine there are 10 federated apps

  1. shell - served with webpack-dev-server from localhost:4242
  2. commonLibs - from localhost:4243
  3. wmf3 - from https://production.com/wmf3
  4. wmf4 - from https://production.com/wmf4
  5. ...

Some are served with webpack-dev-server, others from production (e.g. CDN, AWS Cloudfront, etc.). For these we would like to publish an npm package with types.

It doesn't look like @module-federation/typescript has any configuration.

@ScriptedAlchemy are there any hidden options that we could provide to generate types that are usable when published as an NPM package and includes types for modules that expose node_modules? If not, would it be possible to extend the plugin to accommodate for those use-cases?

steven-pribilinskiy avatar Jun 12 '22 11:06 steven-pribilinskiy

Also it suffers from the issue with dynamic imports image

Even though here are examples in advanced-api and the dynamic System Host. So it's not something unusual to do.

We use the module-federation/external-remotes-plugin to achieve this

Also the Practical Module Federation has an example of Dynamically Loading Federated Modules for Webpack 4 and 5.

@ScriptedAlchemy how we can use the plugin in such environment?

Looks like module-federation/external-remotes-plugin and @module-federation/typescript are currently incompatible

steven-pribilinskiy avatar Jun 12 '22 11:06 steven-pribilinskiy

I've changed the URL to be localhost in remotes section and now we're getting this 404 error for exposed npm package

\node_modules\got\index.js:482
     proxy.emit('error', new got.HTTPError(statusCode, res.statusMessage, res.headers, opts), null, res);
    ^
GotError [HTTPError]: Response code 404 (Not Found)

  path: '/@mf-typescript/ui-library.d.ts',
  protocol: 'http:',
  url: 'http://localhost:9082/@mf-typescript/ui-library.d.ts',
  statusCode: 404,
  statusMessage: 'Not Found',

image

steven-pribilinskiy avatar Jun 12 '22 11:06 steven-pribilinskiy

Sorry for the numerous comments, I wish it can be hosted on Github, with Issues/Discussion tabs It can be an empty repo with a README only.

steven-pribilinskiy avatar Jun 12 '22 11:06 steven-pribilinskiy

We have released a Federated Types Plugin beta.

Theres some gaps in the type def bundling, we are aware of it and working on improvements.

https://app.privjs.com/buy/packageDetail?pkg=@module-federation/typescript

I want to contribute to the repo, is there any way I can join the work? Wait for your reply. @ScriptedAlchemy

MR-BH avatar Jul 08 '22 02:07 MR-BH

I don't know if this was already mentioned in this thread but there are some nice solutions exposed in this blog post: https://spin.atomicobject.com/2022/07/19/typescript-federated-modules/

anthonycaron avatar Aug 12 '22 08:08 anthonycaron

For anyone interested, at Cloudbeds, we've released a public NPM package that was built on top of existing solutions

https://www.npmjs.com/package/@cloudbeds/webpack-module-federation-types-plugin

It's also planned to open source it on Github

steven-pribilinskiy avatar Aug 17 '22 11:08 steven-pribilinskiy

@ScriptedAlchemy Any plan to add typescript support in federated modules?

gauravmakkar avatar Aug 22 '22 12:08 gauravmakkar

Already done. App.privjs.com search for module-federation/typescript

We did this months ago

ScriptedAlchemy avatar Aug 27 '22 04:08 ScriptedAlchemy

https://www.npmjs.com/package/@module-federation/typescript

ScriptedAlchemy avatar Aug 29 '22 02:08 ScriptedAlchemy

I'm searching for a solution with Angular using the new typescript package. I tried to use it but with no success. But great initiative @ScriptedAlchemy

vmagalhaes avatar Aug 29 '22 16:08 vmagalhaes

The @cloudbeds/webpack-module-federation-types-plugin is now available on Github. Feel free to use Issues or Discussions tabs.

And let me thank @ScriptedAlchemy for the awesome Module Federation plugin upon which a lot of projects nowadays started to rely on. I hope that some day there won't be a need for a 3rd party plugin to deal with types and there'll be a standard documented way built into Webpack.next

steven-pribilinskiy avatar Aug 29 '22 20:08 steven-pribilinskiy

If we can merge or open PRs to module-federation/typescript to improve the "official" plugin that would be great. We open-sourced it to try and get more input and help since i dont write TS I just know webpack very well haha

ScriptedAlchemy avatar Aug 29 '22 20:08 ScriptedAlchemy

@ScriptedAlchemy can you link the github repo?

stefan-toubia avatar Aug 29 '22 21:08 stefan-toubia

@ScriptedAlchemy can you link the github repo?

I suppose https://github.com/module-federation/typescript

vadym-oliinyk avatar Aug 30 '22 08:08 vadym-oliinyk

thats the one, PR's welcome

ScriptedAlchemy avatar Sep 09 '22 05:09 ScriptedAlchemy