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

[Question/Bug]: Unable to properly build React application as a consumable UMD library #9616

Closed
jonasmarco opened this issue Mar 10, 2025 · 9 comments

Comments

@jonasmarco
Copy link

Hi guys

I'm trying to build a React application as a UMD library that can be consumed by other React applications. The goal is to generate a single JavaScript file that can be imported via script tag into any React project.

// Configuration attempt
export const outputLib = {
  path: path.resolve(__dirname, '../dist/lib'),
  filename: 'final.js',
  library: {
    name: 'MyLib',
    type: 'umd',
    export: 'default'
  },
  globalObject: 'this'
};

Expected behavior:

  1. Generate a bundled file (final.js) that can be imported via script tag
  2. Other React applications should be able to use the library by:
  3. Including the generated library script
  4. Calling the exported function to render the React application in a specific container

Questions:

  1. What's the correct way to configure Rspack to build a React application as a UMD library?
  2. How should external dependencies (React, ReactDOM) be handled?
  3. Is there a working example of this setup?

Any guidance would be greatly appreciated!

Project here: https://github.com/jonasmarco/rspack-starter

@ThomasChan
Copy link

+1

@alex-vukov
Copy link

This doesn't look like a bug to me. The way to correctly handle your requirements depends on whether you have control of the code of the application where you want to use your library. If you have control of the consumer application you can make it load your application by using Module Federation and then you don't need to build it as a UMD. You can simply put React and ReactDOM as shared dependencies in the Module Federation configuration. If, however, you don't have control of the consumer app or you want to be able to inject your app inside any web application, whether it uses React or doesn't, then you have two choices:

  1. Build as a UMD and mark React and ReactDOM as externals in the rspack config as described here . Have in mind that these externals have to come from somewhere so there must be React and ReactDOM objects in the window object of the browser. In order for them to be there you have to either add React and ReactDOM as scripts using <script> tags or to be sure that the consumer application is sharing them through the window object. Both are not ideal.
  2. Build as a UMD and bundle your app together with React and ReactDOM. There is a downside to this because the UMD will be big since it includes many libraries. I believe this is how your application is building at the moment. In order to make it work you have to first make sure that after you load your app with a <script> tag there is a MyLib object created inside the browser's window object. If it isn't there you can tweak the rspack config globalObject by setting it to window and check again.

@alex-vukov
Copy link

alex-vukov commented Mar 19, 2025

By the way I was able to load your application just by building it as a library and using this index.html. It is rendering itself in the root div. This seems to be incorrect because it's using the main.tsx file instead of the index.tsx file which I see should happen when it is a library. I guess it's not reading your environment variables correctly:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TEST</title>
    <script defer src="./final.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

Image

@jonasmarco
Copy link
Author

By the way I was able to load your application just by building it as a library and using this index.html. It is rendering itself in the root div. This seems to be incorrect because it's using the main.tsx file instead of the index.tsx file which I see should happen when it is a library. I guess it's not reading your environment variables correctly:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TEST</title>
    <script defer src="./final.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
Image

I don't think so. I put a console.log in the output to see the configuration that comes during the build and it came out right? Do you want to test it out? I uploaded an adjustment improving the organization of the variables.

@jonasmarco
Copy link
Author

This doesn't look like a bug to me. The way to correctly handle your requirements depends on whether you have control of the code of the application where you want to use your library. If you have control of the consumer application you can make it load your application by using Module Federation and then you don't need to build it as a UMD. You can simply put React and ReactDOM as shared dependencies in the Module Federation configuration. If, however, you don't have control of the consumer app or you want to be able to inject your app inside any web application, whether it uses React or doesn't, then you have two choices:

  1. Build as a UMD and mark React and ReactDOM as externals in the rspack config as described here . Have in mind that these externals have to come from somewhere so there must be React and ReactDOM objects in the window object of the browser. In order for them to be there you have to either add React and ReactDOM as scripts using <script> tags or to be sure that the consumer application is sharing them through the window object. Both are not ideal.
  2. Build as a UMD and bundle your app together with React and ReactDOM. There is a downside to this because the UMD will be big since it includes many libraries. I believe this is how your application is building at the moment. In order to make it work you have to first make sure that after you load your app with a <script> tag there is a MyLib object created inside the browser's window object. If it isn't there you can tweak the rspack config globalObject by setting it to window and check again.

Are the options not recommended?
Any suggestions on how I can generate the output lib that can be consumed by any other React application effortlessly?

@ThomasChan
Copy link

I couldn't use it in my project tests either. There seems to be an issue with rspack generating UMD. My project itself is based on rspack+react. I packaged some of the components into an SDK using rspack, setting react and react-dom as externals. However, when I put this SDK into a newly created rspack project and import it, it also failed to run either dev or production mode.

However, there are no issues when rspack outputs in the ESM format. I had tested it, and after outputting in the ESM format, my SDK works seamlessly in rspack+react, vite+react, and umi+react projects.

my rspack config

const config = {
    // ...
  output: {
    asyncChunks: false,
    chunkFormat: 'module',
    filename: 'index.js',
    library: {
      type: 'module',
    },
  },
  experiments: {
    outputModule: true,
  },
  externalsType: 'module',
  externalsPresets: {
    web: true,
  },
  externals: [
    {
      "react": "react",
      "react-dom": "react-dom",
    },
  ],
}

@jonasmarco
Copy link
Author

I couldn't use it in my project tests either. There seems to be an issue with rspack generating UMD. My project itself is based on rspack+react. I packaged some of the components into an SDK using rspack, setting react and react-dom as externals. However, when I put this SDK into a newly created rspack project and import it, it also failed to run either dev or production mode.

However, there are no issues when rspack outputs in the ESM format. I had tested it, and after outputting in the ESM format, my SDK works seamlessly in rspack+react, vite+react, and umi+react projects.

my rspack config

const config = {
// ...
output: {
asyncChunks: false,
chunkFormat: 'module',
filename: 'index.js',
library: {
type: 'module',
},
},
experiments: {
outputModule: true,
},
externalsType: 'module',
externalsPresets: {
web: true,
},
externals: [
{
"react": "react",
"react-dom": "react-dom",
},
],
}

I'll try it later, thanks for your help.

@jonasmarco
Copy link
Author

@ThomasChan and @alex-vukov

I think I've done it, check out the latest commit in the project and run it locally to understand and see if you agree with the final result.

@alex-vukov
Copy link

@jonasmarco this looks right and it worked when I tested it

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

3 participants