Skip to content

Instantly share code, notes, and snippets.

@joeldenning
Last active March 4, 2024 09:05
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joeldenning/79f2592086ad132fae8ee5aae054c0b6 to your computer and use it in GitHub Desktop.
Save joeldenning/79f2592086ad132fae8ee5aae054c0b6 to your computer and use it in GitHub Desktop.
create-react-app + react-app-rewired + single-spa
module.exports = {
webpack(config, env) {
config.entry = './src/single-spa-entry.js';
config.output = {
...config.output,
filename: 'project-name.js',
libraryTarget: 'system',
}
config.plugins = config.plugins.filter(plugin => plugin.constructor.name !== 'HtmlWebpackPlugin' && plugin.constructor.name !== 'MiniCssExtractPlugin')
delete config.optimization
return config;
},
devServer(configFunction) {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
config.disableHostCheck = true
config.headers = config.headers || {}
config.headers['Access-Control-Allow-Origin'] = '*'
return config
}
}
}

This Gist works for CRA 3. For CRA 4, you can try community maintained craco plugin for converting to a single-spa application at https://github.com/hasanayan/craco-plugin-single-spa-application (thanks @hasanayan):

  1. Install react-app-rewired, as explained in https://github.com/timarney/react-app-rewired.
  2. Create a file in src called single-spa-entry.js (or tsx for typescript)
  3. Modify config-overrides.js, as shown in the config-overrides.js file provided in this gist.
  4. (Optional) remove src/main.js, since single-spa-entry is the new entry
  5. (Optional) remove public/index.html, since single-spa applications share a single html file, instead of one html file per project.
@mos-adebayo
Copy link

mos-adebayo commented Jul 27, 2020

Thanks for the reply. I tried to add the access-control-allow-origin header before I asked but the problem still persists.

module.exports = function override(config, env) {
 config.entry = "./src/single-spa-entry.js";
 config.output = {
   ...config.output,
   filename: "project-name.js",
   libraryTarget: "system"
 };

 config.devServer = {
   compress: true,
  headers: {
     "Access-Control-Allow-Origin": "*"
   },
   disableHostCheck: true
 };

 delete config.optimization;
 config.plugins = config.plugins.filter(
   plugin => plugin.constructor.name !== "HtmlWebpackPlugin"
 );
 return config;
};

PS: I am starting the single-spa with

react-app-rewired start --https `

I am suspecting the CORS setting is not having effect because the single SPA is not started with webpack-dev-server So I need a way to add the CORS setting for react-app-rewired

webpack-dev-server --https

@joeldenning
Copy link
Author

create-react-app does use webpack dev server, see https://github.com/facebook/create-react-app/blob/bdae9b6874904cf34b982c540847e3d6c32cdf42/packages/react-scripts/scripts/start.js#L37. However, modifying the webpack-dev-server has to be done with a special syntax (docs) that I was unaware of. I have updated the original gist and pushed this example that shows it working.

@mos-adebayo
Copy link

Thanks @joeldenning I tried the sample code, but I am getting unexpected token when I try to load in a root-config application

Screenshot 2020-08-03 at 9 21 53 AM

@joeldenning
Copy link
Author

Hi @mos-adebayo - that error means that the URL for your application is incorrect. When the URL is wrong, web servers often return an HTML file instead of a javascript file, and HTML files start with <. What I would do to debug:

  1. In a separate browser tab, open up the URL for your application. Until you see a javascript file, things won't work.
  2. Check the terminal output for your webpack dev server. It prints the name of the output bundle file and port. Try adjusting the URL to match.

@ani979
Copy link

ani979 commented Aug 15, 2020

I tried above as per your directions:

  1. I cant remove index.html, it says its required.
  2. I also cant remove index.js:
    "Could not find a required file.
    Name: index.js"

Do you have repo where i can see these changes?

@ani979
Copy link

ani979 commented Aug 16, 2020

So, this has finally worked for a vanilla CRA.

  • config.entry = './src/App.js'; has to be config.entry = './src/single-spa-entry.js';

@joeldenning
Copy link
Author

Thanks for the details - I have updated the original post to reference the correct entry.

@oyalhi
Copy link

oyalhi commented Oct 3, 2020

@ani979

So, this has finally worked for a vanilla CRA.

As this gist is pretty much incomplete, do you have an example we can look at please?

@joeldenning
Copy link
Author

joeldenning commented Oct 3, 2020

This gist is not incomplete - I have used it on a CRA project before and it works. I do not plan on creating an example for CRA, since I have created dozens of single-spa examples for years and do not have time to create every example that anyone could ever ask for.

@oyalhi
Copy link

oyalhi commented Oct 3, 2020

@joeldenning of course. I thought about that and that's why I specifically asked @ani979 and not you (since he has a working copy). Maybe it wasn't clear. Thanks for all your work.

@joeldenning
Copy link
Author

Ah I misunderstood your comment 👍

@frankielin2015
Copy link

@joeldenning I noticed that you did not override/ delete MiniCssExtractPlugin in this config. Since MiniCssExtractPlugin is already used in CRA project default config, if we don't do any thing about it, it gives us build code in separate folder js, css, media respectively. However, we'd like to align this config with single-spa webpack config as much as we can. I am wonder if we can do anything here. Thanks !

@joeldenning
Copy link
Author

Thanks @frankielin2015 - I have updated the gist with that

@srinivasan-chandrasekaran
Copy link

@joeldenning - if we have multiple configurations in config-overrides.js, then facing few errors when executing commands like build / run.

const customWebpackConfig = (config, env) => { const singleSpaConfig = Object.assign({}, config, { name: "singlespa", entry: "./src/singleSpaMain.tsx", output: { ...config.output, path: path.resolve(process.cwd(), "build/singlespa"), filename: "bundle.js", libraryTarget: "system", }, }); delete singleSpaConfig.optimization; return [singleSpaConfig, config]; };

Above is my custom config overrides, to have application build to output two different versions. One is normal app and other one in the micro-app with single-spa lifecycle methods.

When i ran npm run build, getting below error

"File sizes after gzip:

ENOENT: no such file or directory, open 'PROJECT DIR/build/bundle.js'
"
It seems, appBuild path reference not updated for updated new entry with output of different folder. Have you faced this issue. Any alternate ways to achieve multiple configuration outputs without any error.

@gogones
Copy link

gogones commented Oct 21, 2020

I tried above as per your directions:

  1. I cant remove index.html, it says its required.
  2. I also cant remove index.js:
    "Could not find a required file.
    Name: index.js"

Do you have repo where i can see these changes?

Me too, how you resolve it?

@gogones
Copy link

gogones commented Oct 21, 2020

Thanks @frankielin2015 - I have updated the gist with that

i got this error when i build with plugin.constructor.name !== 'MiniCssExtractPlugin' and if i remove it the build is success

Screen Shot 2020-10-21 at 17 03 08

@joeldenning
Copy link
Author

It's possible things are broken due to upgrades in create-react-app. I might be able to try things out with the newest version of create-react-app in the next few days and report back with what I find.

@shirly-chen-awx
Copy link

How to fix the error that System is not defined?

@joeldenning
Copy link
Author

How to fix the error that System is not defined?

Are you intending to use SystemJS? If so, here are the webpack modifications required for SystemJS: https://github.com/systemjs/systemjs#compatibility-with-webpack. If not, change the webpack output.libraryTarget to not be System.

@albertocorrales
Copy link

Hi,

first of all, thanks for this gist, it has been really helpful.

I'm trying to create a test app, to load one CRA micro-frontend with Single-Spa. I managed to load the application, but I cannot see the svg image when I load the application. Am I missing anything? this is my full example https://github.com/albertocorrales/single-spa-cra-example

@joeldenning
Copy link
Author

For CRA 4 users, here's a community maintained craco plugin for converting to a single-spa application (thanks @hasanayan):

https://github.com/hasanayan/craco-plugin-single-spa-application

@gabrielcerutti
Copy link

gabrielcerutti commented Aug 27, 2021

Hi all, I'm working on a CRA micro-frontend template, a Container App (aka Main SPA) and a demo app including two micro-frontends with nested routes in case you want to take a look.
Any comment is welcome! Thanks!

@JeffWeim
Copy link

JeffWeim commented Apr 7, 2022

@joeldenning @hasanayan Does the https://github.com/hasanayan/craco-plugin-single-spa-application plugin allow you to run a CRA application as a standalone single-spa application?

@fupengl
Copy link

fupengl commented May 25, 2022

If you use react-app-rewired, you can use react-app-rewired-single-spa, which can be quickly integrated into the react-scripts project, supports 4.x 5.x, and supports hot updates

// use `customize-cra`
const { rewiredSingleSpa, rewiredSingleSpaDevServer } = require("react-app-rewired-single-spa");
const { override, overrideDevServer } = require("customize-cra");

module.exports = {
  webpack: override(
    rewiredSingleSpa({
      orgName: "you",
      projectName: "test",
      reactPackagesAsExternal: true,
      peerDepsAsExternal: true,
      orgPackagesAsExternal: true,
    })
  ),
  devServer: overrideDevServer(rewiredSingleSpaDevServer()),
};

@ThaddeusJiang
Copy link

@sameroso
Copy link

sameroso commented Aug 18, 2022

I could config my cra with rewired with this configuration

the publicPath property loaded my assets correctly in the root frontend

const ASSET_PATH = process.env.ASSET_PATH || 'http://localhost:3000/';

module.exports = {
  webpack(config, env) {
    config.output = {
      ...config.output,
      filename: "mfe-1.js",
      libraryTarget: "system",
      publicPath:ASSET_PATH
    };
    delete config.optimization;
    return config;
  },
  devServer(configFunction) {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost);
      config.disableHostCheck = true;
      config.headers = config.headers || {};
      config.headers["Access-Control-Allow-Origin"] = "*";
      return config;
    };
  },
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment