Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
}
}
}
  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.
@flyyuan

This comment has been minimized.

Copy link

@flyyuan flyyuan commented May 16, 2020

Can you provided single-spa-entry.js example?

@joeldenning

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented May 16, 2020

single-spa-entry.js is shown here: https://single-spa.js.org/docs/ecosystem-react#quickstart.

In a CRA app, your rootComponent is App.js

@BartJanvanAssen

This comment has been minimized.

Copy link

@BartJanvanAssen BartJanvanAssen commented Jun 1, 2020

Very nice! I think I would leave out

  • filename: 'project-name.js'
    and favor:
  • config.output.library = 'project name'
@joeldenning

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Jun 1, 2020

Hi @BartJanvanAssen, I disagree with setting output.library. Since the libraryTarget is system, this will change the output bundle to be a named System.register, which requires an additional SystemJS extra. The named-register systemjs extra has been the single largest source of bugs within systemjs and I have put together >5 fixes for it in the last year. I would prefer users to avoid it possible.

// Without output.library - DESIREABLE
System.register(['react', 'react-dom'], function () {})

// With output.library - NOT DESIREABLE
System.register('app-name', ['react', 'react-dom'], function () {})

You can read more about named systemjs registers at https://github.com/systemjs/systemjs#extras and https://github.com/systemjs/systemjs/blob/master/docs/system-register.md.

Regarding output.filename - using the project name as the filename allows for using a port number to auto-generate an override url, which is very convenient.

@BartJanvanAssen

This comment has been minimized.

Copy link

@BartJanvanAssen BartJanvanAssen commented Jun 1, 2020

Ah! Appreciate it 👍 I was also using UMD, I'll move to 'system'

@utkuturunc

This comment has been minimized.

Copy link

@utkuturunc utkuturunc commented Jun 15, 2020

what would be a good way to include css and images files to this config?

@joeldenning

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Jun 15, 2020

Hi @utkuturunc, good question. Here's one way of adding css and image support:

// Documentation at https://webpack.js.org/loaders/css-loader/#root
config.module.rules.push({
  test: /\.css$/i,
  use: ['style-loader', 'css-loader'],
})

// Documentation at https://webpack.js.org/loaders/file-loader/
config.module.rules.push({
  test: /\.(png|jpe?g|gif|svg)$/i,
  use: [
    {
      loader: 'file-loader',
    },
  ],
})
@filoxo

This comment has been minimized.

Copy link

@filoxo filoxo commented Jun 29, 2020

If an organization were to use multiple applications based on CRA, it may also be desirable to change the the dev-server port and sockPort on each application config so that there are no collisions. This was pointed out by Frikk Fossan on the Slack channel.

const UNIQUE_PORT = 4321 || process.env.PORT;
config.devServer.port = UNIQUE_PORT;
config.devServer.sockPort = UNIQUE_PORT;
@fbatroni

This comment has been minimized.

Copy link

@fbatroni fbatroni commented Jul 3, 2020

Great gist. I think this is what I'm looking for.
Ques - would you still use the npm start command as is "start": "react-app-rewired start", ? I noticed some examples do something like this webpack-dev-server --port 3000

@joeldenning

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Jul 6, 2020

would you still use the npm start command as is "start": "react-app-rewired start", ?

Yes

@mos-adebayo

This comment has been minimized.

Copy link

@mos-adebayo mos-adebayo commented Jul 26, 2020

Great gist! How do you resolve CORS issue. I am getting CORS error when the root-config tries to load the single-spa created. This does not happen for single-spa I created by single-spa CLI
Screenshot 2020-07-26 at 3 30 15 AM

@joeldenning

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Jul 27, 2020

For cors, you need to add the access-control-allow-origin header. I'll update the original gist with this

config.devServer.disableHostCheck = true
config.devServer.headers = config.devServer.headers || {}
config.devServer.headers['Access-Control-Allow-Origin'] = '*'

Reference at https://github.com/single-spa/create-single-spa/blob/d73aafb1b38df0ced8e98016ef5df9c9dfae9ef3/packages/webpack-config-single-spa/lib/webpack-config-single-spa.js#L70-L73

@mos-adebayo

This comment has been minimized.

Copy link

@mos-adebayo 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

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Jul 27, 2020

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

This comment has been minimized.

Copy link

@mos-adebayo mos-adebayo commented Aug 3, 2020

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

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Aug 3, 2020

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

This comment has been minimized.

Copy link

@ani979 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

This comment has been minimized.

Copy link

@ani979 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

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Aug 17, 2020

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

@oyalhi

This comment has been minimized.

Copy link

@oyalhi 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

This comment has been minimized.

Copy link
Owner Author

@joeldenning 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

This comment has been minimized.

Copy link

@oyalhi 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

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Oct 3, 2020

Ah I misunderstood your comment 👍

@frankielin2015

This comment has been minimized.

Copy link

@frankielin2015 frankielin2015 commented Oct 5, 2020

@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

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Oct 13, 2020

Thanks @frankielin2015 - I have updated the gist with that

@csrinivasan80

This comment has been minimized.

Copy link

@csrinivasan80 csrinivasan80 commented Oct 21, 2020

@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

This comment has been minimized.

Copy link

@gogones 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

This comment has been minimized.

Copy link

@gogones 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

This comment has been minimized.

Copy link
Owner Author

@joeldenning joeldenning commented Oct 22, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.