create-react-app icon indicating copy to clipboard operation
create-react-app copied to clipboard

Add more entry points

Open riceyeh opened this issue 7 years ago • 119 comments

In addition to src/index.js, I have other entry points in my app. How do I add them?

riceyeh avatar Nov 22 '16 01:11 riceyeh

You could render to multiple html elements in index.js, is that what you mean?

reactdom.render(<App1/>, div1)
reactdom.render(<App2/>, div2)
reactdom.render(<App3/>, div3)

tbillington avatar Nov 22 '16 02:11 tbillington

@riceyeh I think you asked this already? is there anything here different from https://github.com/facebookincubator/create-react-app/issues/1079

thien-do avatar Nov 22 '16 06:11 thien-do

Can you explain your use case in more detail? While you asked this earlier, I wonder if I misunderstood your use case.

gaearon avatar Nov 22 '16 11:11 gaearon

For example, when preparing multiple apps I might want to do index.html for web and touch-kiosk.html for other devices. Basically using components and composition to do multiple apps. For this I need not just few html files but multiple entry points for webpack.

jkarttunen avatar Nov 23 '16 21:11 jkarttunen

I would like to be able to create multiple bundles.

For example:

admin.bundle.js       # for admin section in my site
client.bundle.js      # for client section 

Now I am forced to create two applications:

create-react-app admin
create-react-app client

This approach seem to be excessive. It would be great if it was possible to separate the bundles within a single application.

kulakowka avatar Dec 16 '16 11:12 kulakowka

Thanks for sharing the use cases. We might support this eventually but for now I recommend either separate apps or ejecting and manually configuring Webpack.

gaearon avatar Dec 16 '16 12:12 gaearon

You could do something funky with shell scripting. Not sure how practical this is but it will solve your problem (from how I interpret it).

Have two entry point js files, lets go with user.js and admin.js.

Have two build commands in package.json; build-user, build-admin.

When you go to run build-user, before running react-scripts build, have some shell code that copies user.js to index.js in your source code. Same for build-admin, just copy it's entry point to index.js before running the actual build command. This way if you're working on the same entry point consistently, you can still use build.

package.json

"build": "react-scripts build",
"build-user": "cp src/user.js src/index.js && react-scripts build",
"build-admin": "cp src/admin.js src/index.js && react-scripts build"

tbillington avatar Dec 17 '16 00:12 tbillington

Maybe we could pass entry point to webpack instead? Then add instructions of shell scripting it.

jkarttunen avatar Dec 22 '16 22:12 jkarttunen

I ran in to a similar issue today, and there appears to be an easier and more elegant workaround to this, similar to what @tbillington had mentioned. Keep in mind, I am pretty new to React/Webpack/Babel/etc, so forgive me any nube mistakes.

You can simulate having multiple HTML files by using URL parameters on the Index.html file. In index.js, you simply parse out the URL param and serve the appropriate React Component into the root div based on that parameter. In this case, the default would be App, but if you load it with

http://www.myhost.com/index.html?startPage=SecondApp

... it will serve up the SecondApp React component.

The App.js and index.html are the boilerplate created by create-react-app.

Index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import SecondApp from './SecondApp';
import './index.css';


// Copied from http:jquery-howto.blogspot.com/2009/09/get-url-parameters-values-with-jquery.html
function getUrlVars() {
  var vars = [], hash;
  var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
  for (var i = 0; i < hashes.length; i++) {
    hash = hashes[i].split('=');
    vars.push(hash[0]);
    vars[hash[0]] = hash[1];
  }
  return vars;
}

var urlParams = getUrlVars();

switch (urlParams["startPage"]) {
  case "SecondApp":
    ReactDOM.render(<SecondApp />, document.getElementById('root'));
    break;

  case undefined:
  default:
    ReactDOM.render(<App />, document.getElementById('root'));
    break;
}

SecondApp.js

import React, { Component } from 'react';

class SecondApp extends Component {
    constructor() {
        super();
    }
    render() {
        return <div>MY SECOND APP!</div>;
    }
}

export default SecondApp;

This way, everything will get packaged, minified, transmorgrifacated, etc. It seems a lot more palatable to do it this way than ejecting entirely. If you have legacy HTML/Javascript that isn't part of the module system, you put that in the public directory (and subdirectories), and Webpack will just copy it untouched.

Let me know if this is helpful.

Nick

carruthe avatar Jan 17 '17 19:01 carruthe

I'm also trying to implement a User / Admin scenario.

@carruthe, I believe your approach is not working for me because the User part is simple and should be as light weight (code size wise) as possible as it's expected to be accessed mainly on mobile. The Admin part is more complex with more dependencies and expected to be accessed predominantly on desktop. I'd therefore prefer separate JS bundles for Client and Admin.

@tbillington, I think your suggestion should work with a bit of extra scripting to merge the separate build outputs into one, or am I passed duct-tape-strength?

juliaogris avatar Jan 26 '17 10:01 juliaogris

For admin/user use case you can use require.ensure(dependencies, callback) to load module async. more detail

but I like create-react-app support multiple entry/bundle.

smmoosavi avatar Feb 02 '17 19:02 smmoosavi

I agree it's something we'd like to see in the future.

gaearon avatar Feb 09 '17 00:02 gaearon

I've implemented this as proof of concept, so you can try it already by installing [email protected] on an existed CRA project:

  1. npm uninstall --save-dev react-scripts
  2. npm install --save-dev [email protected]

To add a new entry point:

  1. Create the ./pages folder in the root of project and put in them a *.js file (for example ./pages/admin.js)
  2. npm run start (npm run build also works)
  3. Open http://localhost:3000/admin.html 🎉

Also I think it's a good first step for prerendred pages. All we need now is to generate *.html pages differently (see static-site-generator-webpack-plugin). What do think about the API?

Changes: https://github.com/stockspiking/create-react-app/commit/fced96efe4d2c717ec95f48548998b5da7e03dcc

Known issues of this POC
  1. Dev server doesn't recompile after adding new entries
  2. New separated chunk /static/js/polyfills.js may be added after other scripts.
  3. It's not possible to change html template

kohver avatar Mar 10 '17 12:03 kohver

@kohver Do you think it would be best to have a separate html file or using react-router to lazy load those extra js required. Something like this http://moduscreate.com/code-splitting-for-react-router-with-es6-imports/

andymac4182 avatar Mar 14 '17 10:03 andymac4182

My use case of having multiple entry points is that i'm developing plugins for cloud solutions of atlassian (jira, confluence) and all plugins injected into the system by iframe, so in different places like admin panel, main page panel, sections inside issues e.t.c. i need html page, since i want to use react i need to split my app into multiple pieces. It's quite inconvinient to create different folders for each section of the whole app bcs they could be very small and it would be easier to split them into folders by page or section. So the only way to me (for now) is to create my own build process.

Alexter-progs avatar Mar 23 '17 20:03 Alexter-progs

I also have a similar use case as Alexter-progs. I am developing a chrome extension which requires both background.js and inject.js files. At present, there isn't a way for me to generate separate bundles to be able to make a chrome extension.

dan-kez avatar May 07 '17 12:05 dan-kez

If you ask me, application should be separated into logical bundles and modules. It may sound a bit complicated, but let me explain.

For example, we want to build large application, with frontend (landing page), customer dashboard and admin dashboard.

We'll have 3 bundles, let's call them frontend, customer, admin. They are nothing but entry-points, only containing routes. Bundles should be able to reuse code, e.g. frontend will display plans for customers, which is edited/maintained in admin dashboard. Now we're approaching modules.

Module - set of components/reducers/middlewares/actions/etc grouped by logic, e.g. Users, Plans, Notifications, UIKit. Each module could communicate with each other and also be used by bundles only via single index.js containing all module's exports. Each module should be loaded on-demand, so we separate each bundle and module into it's own chunk.

I would be really happy if we can have current create-react-app structure as simple application and smth called advanced similar to I've described above. E.g. create-react-app foo creates simple app by default, but if we use create-react-app foo --advanced it would switch to advanced mode.

/cc @gaearon

miraage avatar May 22 '17 21:05 miraage

Hi, using webpack built-in MultiCompiler it is quite easy to run different builds at the same time (including shared cache and quick rebuilding when watching).

In the most basic setup it would use the same index.html template and generate index.html, index2.html and indexanything.html from index.js, index2.js and indexanything.js, with minimal page-specific bundles, but using the same output dirs for assets. That would necessitate changing only a few lines in create-react-app react-scripts. I could send a PR as a discussion point, so we could see how it affects testing, routing etc? Somebody more knowledgeable in create-react-app customs could continue from there.

It might also be possible to configure source templates, output paths, etc, from package.json fields or file system structure (such as src/pages/indexname/index.js), but I would guess that outputting properly to different paths would need more involved changes depending on how paths are handled in build scripts. I could look into this briefly too, if you have opinions on how it should work...

plievone avatar Jun 11 '17 18:06 plievone

I met the same problem, here is my way, it works :

Four steps to add multiple entry points to create-react-app

(say we add an admin.html):

1. Eject the project

npm run eject  

2. Add multiple entry to webpack.config.dev.js

entry: {
    index: [
      require.resolve('react-dev-utils/webpackHotDevClient'),
      require.resolve('./polyfills'),
      require.resolve('react-error-overlay'),
      paths.appIndexJs,
    ],
    admin:[
      require.resolve('react-dev-utils/webpackHotDevClient'),
      require.resolve('./polyfills'),
      require.resolve('react-error-overlay'),
      paths.appSrc + "/admin.js",
      ]
  },
  output: {
    path: paths.appBuild,
    pathinfo: true,
    filename: 'static/js/[name].bundle.js',
    chunkFilename: 'static/js/[name].chunk.js',
    publicPath: publicPath,
    devtoolModuleFilenameTemplate: info =>
      path.resolve(info.absoluteResourcePath),
  },

3. Modify HtmlWebpackPlugin

add a new plugin node:

    new HtmlWebpackPlugin({
      inject: true,
      chunks: ["index"],
      template: paths.appHtml,
    }),
    new HtmlWebpackPlugin({
      inject: true,
      chunks: ["admin"],
      template: paths.appHtml,
      filename: 'admin.html',
    }),

4. webpack Dev Server

rewrite urls /config/webpackDevServer.config.js:

    historyApiFallback: {
      disableDotRule: true,
      // 指明哪些路径映射到哪个html
      rewrites: [
        { from: /^\/admin.html/, to: '/build/admin.html' },
      ]
    }

detail: http://imshuai.com/create-react-app-multiple-entry-points/

maoshuai avatar Jun 15 '17 13:06 maoshuai

Thanks @maoshuai. Works perfectly!

BrunoFenzl avatar Jul 19 '17 11:07 BrunoFenzl

@maoshuai, Worked out well, thanks. Though I had to change the value of filename to 'admin.html', without the build/ like this...

 new HtmlWebpackPlugin({
      inject: true,
      chunks: ["admin"],
      template: paths.appHtml,
      filename: 'admin.html',
    }),

Sowed avatar Jul 26 '17 08:07 Sowed

@Sowed You are right, I had amended it.

maoshuai avatar Aug 31 '17 14:08 maoshuai

Have some effects when change the output file name? The filename is "bundle.js" definitely in react-dev-utils/webpackHotDevClient.js. https://github.com/facebookincubator/create-react-app/blob/master/packages/react-dev-utils/webpackHotDevClient.js#L36

codering avatar Sep 09 '17 01:09 codering

HI guys. I'm just wonder how you feel about extending webpack.config? Same or similar way as Storybook did it https://storybook.js.org/configurations/custom-webpack-config/? In that way you'll give more power to the end user. I can imagine something like this will kick off creating plugins for react-create-app and it could became quite conflicting. However, I believe this could be handled by some conflict message which warn user whenever overwriting property trying to overwrite already overwritten property. Thanks

kresli avatar Sep 26 '17 14:09 kresli

@maoshuai What changes might one need to make if using a library and libraryTarget in the webpack output?

elzii avatar Oct 18 '17 22:10 elzii

Hi guys.

My use case is this: When I create a new component and I want to actually release it standalone, I still need an app as a testbed/dev environment. One solution would be to use storybook, another - what I do - is create a separate project with create-react-app, and then I have two projects, e.g. my-component and my-component-examples. It works, but it's clumsy.

I'd prefer having src/examples and src/my-component with the ability to build e.g. just the component á la "build": "react-scripts build src/my-component".

By the way, the electron-webpack project has an interesting approach. It's not perfect yet, and it would require quite some hard-wiring inside create-react-app, but: As a user we define a custom webpack config, e.g. in package.json, and that's justthe path to a .js file that simply exports a partial configuration, and under the hood it gets merged via webpack-merge. What's missing in the electron-webpack approach (as of now) is the abiity to specify the merge strategy from outside, it's using merge.smart automatically so it's easy to add stuff (loaders, entry points), but you cant replace anything.

Anyways, just a suggestion. The ergonomy of that approach is great: As a user, you just use what's provided and don't care, until you do care and add some modifications to the default config.

loopmode avatar Nov 01 '17 07:11 loopmode

I also want this since I have multiple applications in my single CRA setup (since I have a lot of shared components). I tried this approach:

import AsyncLoad from './components/AsyncComponent' // this is basically react-loadable

const App = AsyncLoad({ loader: () => import(`${condition? './App1' : './App2'}`) });

ReactDOM.render(<App />,
  document.getElementById('root')
)

The problem is that webpack is bundling this stuff a lot of time. If I do simple imports, it bundles in below a minute.

themre avatar Nov 01 '17 07:11 themre

@themre You could improve that by using compile-time conditional switches. Then webpack won't bundle all possibilities. E.g:

importApp() {
    switch (process.env.MY_CONDITION) {
       case 'App1': return import('./App1');
       case 'App2': return import('./App2');
    }
}

The trick is to have explicit and static imports. I think this should work because Uglify pluin removes dead code after static analysis.

loopmode avatar Nov 01 '17 08:11 loopmode

@loopmode you mean the webpackConfig option (as for the custom webpack config file)? I could imagine that the custom webpack file could either export a plain object or a function. In case of a plain object, that could be merged. In case of a function, that function could receive the current webpack configuration and is expected to return a new webpack configuration. Then you could do whatever you want with your webpack configuration. (webpack itself allows for exporting a function too)

rmoorman avatar Nov 01 '17 08:11 rmoorman

@rmoorman Yes absolutely, that works too. Just receive the config, modify it and return it again. In that case, webpack-merge doesn't even have to be known to create-react-app - we "users" could decide whether we need it or not, as in many cases it would be enough to just modify the given config a little.

loopmode avatar Nov 01 '17 08:11 loopmode

Any update on this? Using a similar approach to @tbillington at the moment

What i've done differently, is make sure that the original index.js file remains untouched. This way, it's business as usual, unless you want to build your alternate app.

    "build:app2": "cp src/index.js src/index-default.js && cp src/app2.js src/index.js && react-scripts build && cp src/index-default.js src/index.js",

What this does:

  • Step 1: copy index.js to index-default.js
  • Step 2: copy app2.js to index.js
  • Step 3: run the build script
  • Step 4: copy index-default.js back to index.js

Then, you can have an app2.js file that only builds when you run npm run build:app2

Does the job for now!

GioLogist avatar Dec 07 '17 02:12 GioLogist

@GioLogist

My opinion is this is incredibly convoluted. You are trying to use a simple tool (CRA) for a more complex use case (multiple entry points). It's just not supported.

Doing something like https://github.com/facebookincubator/create-react-app/issues/1084#issuecomment-349846916 means you lose the simplicity. You might at that point eject and/or maintain your own fork.

gaearon avatar Jan 08 '18 14:01 gaearon

Hey, I'm hoping I can get some advice. I'm successfully following this technique outlined by @maoshuai 👍 but when it comes to the npm run build I'm a little stuck.

I've been tweaking the webpack.config.prod.js. So far I've added the following into the entry object:

entry: {
    polyfills: require.resolve('./polyfills'),
    vendors: require.resolve('./vendor'),
    runner: paths.appSrc + '/admin.js', // this is my new bundle
    main: paths.appIndexJs,
  }

…which generates the right bundle file, but when it comes to the resulting index.html and admin.html - the index contains the admin bundle - and doesn't boot, and the admin.html matches the index.html file - obviously not the desired result.

How do I configure the production build of admin.html to use the correct bundle?

remy avatar Jan 17 '18 18:01 remy

@remy , I haven't tried your setup of the entry object, but a I curated a similar setup in prod, like the one @maoshuai presented in webpack.config.dev.js. By stripping out the hot reloader and error overlays and using multiple html webpack plugins, it just works.

in webpack.config.prod.js

  entry: {
    index: [
      require.resolve('./polyfills'),
      paths.publicJs, //in paths.js publicJs: resolveApp('src/index.js'),
    ],
    admin: [
      require.resolve('./polyfills'),
      paths.adminJs, //in paths.js adminJs: resolveApp('src/admin.js'),
    ],
  },
  output: {
    path: paths.appBuild,
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
    publicPath: publicPath,
    devtoolModuleFilenameTemplate: info =>
      path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, '/'),
  },

and also remember to split the HtmlWebpackPlugins

  module: {
    ...
  },
  plugins: [
    ...
    // Generates an `index.html` file with the <script> injected.
    new HtmlWebpackPlugin({
      inject: true,
      chunks: ['index'],
      template: paths.publicHtml, // paths.js publicHtml: resolveApp('public/index.html')
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    
    // Generates an `admin.html` file with the <script> injected.
    new HtmlWebpackPlugin({
      inject: true,
      chunks: ['admin'],
      template: paths.adminHtml, // paths.js adminHtml: resolveApp('public/admin.html')
      filename: 'admin.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    ...
  ]

#PS, it's still painful though upgrading react and cra after the eject, having to retouch files to make some things work. I just wish inbuilt support for multiple entry points ships soon.

Sowed avatar Jan 18 '18 06:01 Sowed

@gaearon adding a feature to pass entry point as param to 'react-scripts build' will be a good feature right.

package.json

"build": "react-scripts build",
"build-admin": "react-scripts build src/admin.js",
"build-kiosk": "react-scripts build src/kiosk.js"

or

"build": "react-scripts build",
"build-admin": "react-scripts build --entry src/admin.js",
"build-kiosk": "react-scripts build --entry src/kiosk.js"

build directory

build                               
├── asset-manifest.json             
├── favicon.ico                     
├── images                          
├── static
├── index.html
├── admin                        
│   ├── index.html
├── kiosk                        
│   ├── index.html

kiranps avatar Feb 09 '18 06:02 kiranps

In the meantime, a way around this is to pass through environment variables that determine which file to include for your app and then specify multiple builders in your package.json. eg:

package.json

...
"start": "react-scripts start",
"start-mobile": "REACT_APP_WHICH_APP=mobile npm start",
"build": "react-scripts build",
"build-all": "npm run build-mobile && npm run build",
"build-mobile": "REACT_APP_WHICH_APP=mobile npm run build && mv build build-mobile",

App.js

switch (process.env. REACT_APP_WHICH_APP) {
  case 'mobile':
    RootScreen = require('./screens/mobile/RootScreen').default;
    break;

  default:
    RootScreen = require('./screens/desktop/RootScreen').default;
    break;
}

export default RootScreen;

Remember your environment variables must begin with REACT_APP_ in order for them to be picked up.

martsie avatar Feb 14 '18 04:02 martsie

That seems like a totally valid solution, not just a workaround. One could have a file entry.js and use REACT_APP_ENTRY in the switch-case there.

loopmode avatar Feb 18 '18 12:02 loopmode

I have a general purpose "App Variants" feature in my fork. It works like react-native's .ios vs .android, but is configurable, like this:

app/
  package.json
    "targets": { // configure variants
      "ios": {   // configure ios variant
        "jsExts": [".ios.js", ".cor.js"],
        "appHtml": "index.cor.html"
      },
      "android": {   // configure android variant
        "jsExts": [".android.js", ".cor.js"],
        "appHtml": "index.cor.html"
      },
    },
    "scripts": {
      "build": "react-scripts build", // standard build
      "build:android": "TARGET=android react-scripts build",  // build android
      "build:ios": "TARGET=ios react-scripts build"  // build ios
    }
  src/
    App.js
      import comp1 from './comp1';
      import comp2 from './comp2';
    comp1.js               // use in standard build
    comp1.android.js // use in TARGET=android build
    comp1.ios.js         // use in TARGET=ios build
    comp2.js              // use in standard build
    comp2.cor.js        // use in both ios and android builds
  public/
    index.html // use in standard build
    index.cor.html // use in TARGET=ios and TARGET=android build
  build/  // prod output for standard build
  build_android/  // prod output for TARGET=android build
  build_ios/  // prod output for TARGET=ios build

I'd PR that here if it has any support.

bradfordlemley avatar Feb 18 '18 13:02 bradfordlemley

I had the same problem developing Chrome extensions with React, and there is my workaround.

  • Add additional html file to /public directory. It has to be almost the same as index.html except following changes

    • Change id of root element so for every entry point it has to be uniq
    <div id="options-root"></div>
    
    • Manually add
  • Last change in index.js file. I added condition based on root element and load modules dynamically.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import registerServiceWorker from './registerServiceWorker';

const rootApp = document.getElementById('root');
const optionsApp = document.getElementById('options-root');

if (rootApp) {
  import('./App').then(({ App }) => 
    ReactDOM.render(<App />, rootApp));
} else if (optionsApp) {
  import('./Options').then(({ Options }) =>
    ReactDOM.render(<Options />, optionsApp));
}

registerServiceWorker();

avorobiov avatar Mar 05 '18 09:03 avorobiov

@avorobiov This is my favorite approach so far :) This will only work in dev, though, right? (Since /static/js/bundle.js doesn't have a fingerprint in the name.)

jessepinho avatar Mar 08 '18 09:03 jessepinho

For one possible approach - Neutrino has the concept of a mains pref: https://neutrino.js.org/api/#optionsmains

Which is then used for:

Whilst we are reluctant to add too many options to Neutrino, making users manually override every case where the entry point is referenced was pretty burdensome, so it seemed worth the trade-off.

edmorley avatar Mar 15 '18 12:03 edmorley

Also, neutrino is working on a web-extension preset (module). The purpose is to build a new work-flow (files organization, start script...) for Web Extension.

Morikko avatar Mar 22 '18 16:03 Morikko

There is a (IMHO) very elegant solution to this problem that doesn't require ejecting by making use of react-app-rewired.

If you want to include an entry for an "admin" page for instance, then simply add a config-overrides.js file to your project root with the following contents:

// config-overrides.js
const paths = require('react-scripts/config/paths');
const path = require('path');
const publicPath = paths.servedPath

// Define the paths to your javascript & HTML files
// admin.js should be in your src folder
// admin.html should be in your public folder
paths.adminJs = paths.appSrc + '/admin.js';
paths.adminHtml = paths.appPublic + '/admin.html';

// Utility function to replace plugins in the webpack config files used by react-scripts
const replacePlugin = (plugins, nameMatcher, newPlugin) => {
  const pluginIndex = plugins.findIndex((plugin) => {
    return plugin.constructor && plugin.constructor.name && nameMatcher(plugin.constructor.name);
  });

  if (pluginIndex === -1) {
    return plugins;
  }

  const nextPlugins = plugins.slice(0, pluginIndex).concat(newPlugin).concat(plugins.slice(pluginIndex + 1));

  return nextPlugins;
};

module.exports = function override(config, env) {
  // Define the entries for index & admin
  config.entry = {
    index: [
      require.resolve('react-scripts/config/polyfills'),
      paths.appIndexJs,
    ],
    admin: [
      require.resolve('react-scripts/config/polyfills'),
      paths.adminJs,
    ]
  };

  // Split code into chuncks for index & admin
  config.output = {
    path: paths.appBuild,
    pathinfo: true,
    filename: 'static/js/[name].bundle.js',
    chunkFilename: 'static/js/[name].chunk.js',
    publicPath: publicPath,
    devtoolModuleFilenameTemplate: info =>
      path.resolve(info.absoluteResourcePath),
  };
  
  // Create a HTML entry for index that only includes code for the index bundle
  const indexHtmlPlugin = new HtmlWebpackPlugin({
    inject: true,
    chunks: ["index"],
    template: paths.appHtml,
  });

  // Create a HTML entry for admin that only includes code for the admin bundle
  const adminHtmlPlugin = new HtmlWebpackPlugin({
    inject: true,
    title: 'Admin page',  
    chunks: ["admin"],
    template: paths.adminHtml,
  });

  // Replace the default HTML entry in the webpack config used by react-scripts with the index HTML entry
  replacePlugin(config.plugins, (name) => /HtmlWebpackPlugin/i.test(name), indexHtmlPlugin);

  // And finally add the second HTML plugin for your Admin page.
  config.plugins.push(adminHtmlPlugin);
  return config;
};

These overrides create an index.html and an admin.html file in your build folder containing just the code chuncks they need, without needing to eject, so you retain all the conveniences provided by create-react-app.

The replacePlugin function was written by @ngotchac => see his comment

poupapaa avatar May 30 '18 15:05 poupapaa

@poupapaa I like your solution, so I followed your instruction however in my build/, my index.html is including the css and js for the other page instead of the chunks I specified in the HTMLWebPackPlugin config.

Can you help me? Here's my config-override.js

// config-overrides.js
const paths = require('react-scripts/config/paths');
const path = require('path');
const publicPath = paths.servedPath;
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Define the paths to your javascript & HTML files
// glrl.js should be in your src folder
// glrl.html should be in your public folder
paths.glrlJs = paths.appSrc + '/glrl.js';
paths.glrlHtml = paths.appPublic + '/glrl.html';

// Utility function to replace plugins in the webpack config files used by react-scripts
const replacePlugin = (plugins, nameMatcher, newPlugin) => {  
  const pluginIndex = plugins.findIndex((plugin) => {
    return plugin.constructor && plugin.constructor.name && nameMatcher(plugin.constructor.name);
  });

  if (pluginIndex === -1) {
    return plugins;
  }

  const nextPlugins = plugins.slice(0, pluginIndex).concat(newPlugin).concat(plugins.slice(pluginIndex + 1));

  return nextPlugins;
};

module.exports = function override(config, env) {
  // Define the entries for index & glrl
  config.entry = {
    index: [
      require.resolve('react-scripts/config/polyfills'),
      paths.appIndexJs,
    ],
    glrl: [
      require.resolve('react-scripts/config/polyfills'),
      paths.glrlJs,
    ]
  };

  // Split code into chuncks for index & glrl
  config.output = {
    path: paths.appBuild,
    pathinfo: true,
    filename: 'static/js/[name].bundle.js',
    chunkFilename: 'static/js/[name].chunk.js',
    publicPath: publicPath,
    devtoolModuleFilenameTemplate: info =>
      path.resolve(info.absoluteResourcePath),
  };
  
  // Create a HTML entry for index that only includes code for the index bundle
  const indexHtmlPlugin = new HtmlWebpackPlugin({
    inject: true,
    title: 'Double Up',  
    chunks: ["index"],
    template: paths.appHtml,
  });

  // Create a HTML entry for glrl that only includes code for the glrl bundle
  const glrlHtmlPlugin = new HtmlWebpackPlugin({
    inject: true,
    title: 'Green Light Red Light',  
    chunks: ["glrl"],
    template: paths.glrlHtml,
  });

  // Replace the default HTML entry in the webpack config used by react-scripts with the index HTML entry
  replacePlugin(config.plugins, (name) => /HtmlWebpackPlugin/i.test(name), indexHtmlPlugin);

  // And finally add the second HTML plugin for your glrl page.
  config.plugins.push(glrlHtmlPlugin);
  
  return config;
};

carolozar avatar Jun 04 '18 22:06 carolozar

I did some digging and it looks like replacePlugin function was not able to successfully replace the original config for index.html. Still looking for a solution to override/replace it.

Update: Fixed now. Updated the replacePlugin line to be:

config.plugins = replacePlugin(config.plugins, (name) => /HtmlWebpackPlugin/i.test(name), indexHtmlPlugin);

to make sure the new HTML config are used.

carolozar avatar Jun 04 '18 23:06 carolozar

Sorry @carolozar, just now noticed your comment. Well spotted & nice fix, kudos! :+1:

poupapaa avatar Jun 14 '18 21:06 poupapaa

@gaearon

My opinion is this is incredibly convoluted.

Wouldn't enabling custom webpack configuration, maybe using something like webpack-merge be a lot less convoluted way of enabling this (without ejecting or forking CRA)

jkarttunen avatar Jun 22 '18 19:06 jkarttunen

@poupapaa @carolozar

Hi, can I ask for help? seems I have a problem when building my application, it does not add a code "JS" and "CSS" in admin.html or index.html inside the build folder, Please see below my code:

const paths = require('react-scripts/config/paths');
const path = require('path');
const publicPath = paths.servedPath;
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Define the paths to your javascript & HTML files
// overriding index to member and add admin
// [name].js should be in your src folder
// [name].html should be in your public folder

paths.adminJs = paths.appSrc + '/admin/index.js';
paths.adminHtml = paths.appPublic + '/admin.html';
paths.appIndexJs = paths.appSrc + '/member/index.js';

// Utility function to replace plugins in the webpack config files used by react-scripts
const replacePlugin = (plugins, nameMatcher, newPlugin) => {  
  const pluginIndex = plugins.findIndex((plugin) => {
    return plugin.constructor && plugin.constructor.name && nameMatcher(plugin.constructor.name);
  });
  
  if (pluginIndex === -1)
    return plugins;
  
  const nextPlugins = plugins.slice(0, pluginIndex).concat(newPlugin).concat(plugins.slice(pluginIndex + 1));

  return nextPlugins;
};

function rewire(config, env) {
  // check and get portal value: member or admin
  const portal = process.env.REACT_APP_PORTAL || 'member'

  // Define the entries for index & admin
   config.entry = {
    index: [
      require.resolve('react-scripts/config/polyfills'),
      paths.appIndexJs,
    ],
    admin: [
      require.resolve('react-scripts/config/polyfills'),
      paths.adminJs,
    ]
  };

  // Split code into chuncks for index & admin
  config.output = {
    path: paths.appBuild,
    pathinfo: true,
    filename: 'static/js/[name].bundle.js',
    chunkFilename: 'static/js/[name].chunk.js',
    publicPath: publicPath,
    devtoolModuleFilenameTemplate: info =>
      path.resolve(info.absoluteResourcePath),
  };

  const htmlTemplates = {
    member: paths.appHtml,
    admin: paths.adminHtml,
  }

  // Create a HTML entry for index that only includes code for the index amd admin bundle
  const htmlPluginConfig = new HtmlWebpackPlugin({
    inject: true,
    chunks: [portal],
    template: htmlTemplates[portal],
  });

  // Replace the default HTML entry in the webpack config used by react-scripts with the index HTML entry
  config.plugins = replacePlugin(config.plugins, (name) => /HtmlWebpackPlugin/i.test(name), htmlPluginConfig);

  return config
}

module.exports = rewire

`

suarezph avatar Jun 28 '18 15:06 suarezph

Thank you @poupapaa for the perfect solution, but I found that the devServer config was not rewritten. By reading the document of react-app-rewired, I found the way to modify devServer config by config-override.js.

Here is my config-override.js

const paths = require('react-scripts/config/paths');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const rewireLess = require('react-app-rewire-less-modules');

// 多页配置:增加入口
paths.appAdminJs = paths.appSrc + '/admin.js';

const replacePlugin = (plugins, nameMatcher, newPlugin) => {
  const pluginIndex = plugins.findIndex((plugin) => {
    return plugin.constructor && plugin.constructor.name && nameMatcher(plugin.constructor.name);
  });

  if (-1 === pluginIndex) {
    return plugins;
  }

  const nextPlugins = plugins.slice(0, pluginIndex).concat(newPlugin).concat(plugins.slice(pluginIndex + 1));

  return nextPlugins;
};

module.exports = {
  webpack: (config, env) => {
    if ('development' === env) {
      config.entry = {
        index: [
          require.resolve('react-scripts/config/polyfills'),
          require.resolve('react-dev-utils/webpackHotDevClient'),
          paths.appIndexJs,
        ],
        admin: [
          require.resolve('react-scripts/config/polyfills'),
          require.resolve('react-dev-utils/webpackHotDevClient'),
          paths.appAdminJs,
        ]
      };
      // 多页配置:区分开入口文件
      config.output.filename = 'static/js/[name].bundle.js';

      const indexHtmlPlugin = new HtmlWebpackPlugin({
        inject: true,
        template: paths.appHtml,
        // 多页配置:2.x 版本默认开启 optimization ,chunks 中需要增加 'vendors', 'runtime~admin'
        chunks: ['vendors', 'runtime~index', 'index'],
        filename: 'index.html',
      });
      const adminHtmlPlugin = new HtmlWebpackPlugin({
        inject: true,
        template: paths.appHtml,
        chunks: ['vendors', 'runtime~admin', 'admin'],
        filename: 'admin.html',
      });
      // 多页配置:
      config.plugins = replacePlugin(config.plugins, (name) => /HtmlWebpackPlugin/i.test(name), indexHtmlPlugin);
      config.plugins.push(adminHtmlPlugin);
    } else {
      config.entry = {
        index: [require.resolve('react-scripts/config/polyfills'), paths.appIndexJs],
        admin: [require.resolve('react-scripts/config/polyfills'), paths.appAdminJs],
      };
      const indexHtmlPlugin = new HtmlWebpackPlugin({
        inject: true,
        template: paths.appHtml,
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true,
        },
        chunks: ['vendors', 'runtime~index', 'index'],
        filename: 'index.html',
      });
      const adminHtmlPlugin = new HtmlWebpackPlugin({
        inject: true,
        template: paths.appHtml,
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true,
        },
        chunks: ['vendors', 'runtime~admin', 'admin'],
        filename: 'admin.html',
      });
      // 多页配置:替换 index.html 生成规则
      config.plugins = replacePlugin(config.plugins, (name) => /HtmlWebpackPlugin/i.test(name), indexHtmlPlugin);
      // 多页配置:添加 plugin 生成 admin.html
      config.plugins.push(adminHtmlPlugin);
    }
    return config;
  },
  devServer: (configFunction) => {
    return (proxy, allowedHost) => {
      const config = configFunction(proxy, allowedHost);
      // 多页配置: 指明哪些路径映射到哪个html
      config.historyApiFallback.rewrites = [
        { from: /^\/admin.html/, to: '/build/admin.html' },
      ];
      return config;
    };
  },
};

xiaosongxiaosong avatar Jul 17 '18 03:07 xiaosongxiaosong

Hi! I created a package to make it a bit more convenient: https://github.com/tadatuta/rewire-entry

tadatuta avatar Aug 16 '18 09:08 tadatuta

@gaearon, isn't this a stupid simple problem to solve or am I missing something? Willing to submit a PR if this looks about right to you, but I understand if there are some concerns about introducing a vendor.js alongside n number of entry points. Either way, we should talk it out. Looking for some guidance before committing too much time into this.

This also opens up a whole new topic/concern about bundle optimization and if we want CRA to have any of that at all. If we did, I might suggest a similar setup to what we use at Nordstrom Rack | HauteLook, which provides the following bundles:

  1. vendor.js all 3rd-party code from the node_modules folder. Large file, but cached and very rarely changes.
  2. common.js contains common chunks of business code between multiple entry points. Changes pretty much every deploy, but not likely to be near as big as vendor.js.
  3. <entry-point>.js contains mostly code specific only to that entry point. Should be a pretty small file and only changes if that entry point has been changed.

The webpack configuration should look something like this:

// webpack.config.babel.js
import { compact, keys } from 'lodash';

const isDev = NODE_ENV === 'development';

export default {
    plugins: compact([
        isDev && new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            minChunks: 3,
        }),
        !isDev && new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            chunks: keys(browserMainEntries),
            minChunks: 3,
        }),
        !isDev && new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            chunks: ['common'],
            minChunks: isExternal,
        }),
    ]),
}

function isExternal({ context }) {
    return context && context.indexOf('node_modules') !== -1;
}

Edit: browserMainEntries represents the list of entry points (e.g., home.js, catalog.js, about.js). You can get this by scanning a directory in which you only place files for each entry point.

jednano avatar Sep 20 '18 07:09 jednano

I also believe that adding this could add way too much complexity to create-react-app.

Recently I needed different entry points to export an app to both electron and the web. Since the electron variant has access to node.js APIs that aren't available on browsers it was impossible to have the same bundle on both platforms. I can't put require statements inside if blocks (I think these get hoisted by webpack), so all of the workarounds posted here aren't useful for me.

Eventually I just decided to fork react-scripts and implement this feature for myself.

This is actually really easy to achieve if you decide to override the entry point using environment variables.

At the top of packages/react-scripts/config/paths.js I declare:

 const entryPoint = process.env.ENTRY || 'index.js'; 

Then in that same file I replaced every "index.js" occurrence with "entrypoint like this:

-  appIndexJs: resolveApp('src/index.js'),
+  appIndexJs: resolveApp(`src/${entryPoint}`)

If I run yarn start I get the same behavior as usual, index.js is the entry point. But, if run ENTRY=custom-index.js yarn start the the entry point is now custom-index.js. Easily switching between variants just by changing a variable is very nice. I haven't tested this exhaustively yet, but it start and build work pretty great so far.

If this feature were to be added into create-react-app I think that this is probably the best approach. The change is small and it doesn't break things for anyone.

GAumala avatar Oct 17 '18 23:10 GAumala

+1 for #5671 , this issue almost two years old, is it possible to merge this one?

seven1240 avatar Nov 17 '18 10:11 seven1240

@GAumala @lepture since we also want to change the index html can we change like this?

20a21
> const envEntryPoint = process.env.ENTRY_POINT || 'index';
82,83c83,84
<   appHtml: resolveApp('public/index.html'),
<   appIndexJs: resolveModule(resolveApp, 'src/index'),
---
>   appHtml: resolveApp('public/' + envEntryPoint + '.html'),
>   appIndexJs: resolveModule(resolveApp, 'src/' + envEntryPoint),
104,105c105,106
<   appHtml: resolveApp('public/index.html'),
<   appIndexJs: resolveModule(resolveApp, 'src/index'),
---
>   appHtml: resolveApp('public/' + envEntryPoint + '.html'),
>   appIndexJs: resolveModule(resolveApp, 'src/' + envEntryPoint),

seven1240 avatar Nov 17 '18 10:11 seven1240

If you go this way, I'd also like to change the .env location. Maybe it is more simple to change the appDirectory value used by resolveApp ? https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/config/paths.js#L17

alex-pex avatar Nov 20 '18 15:11 alex-pex

This could work with naming convention, as it does now. index.js --> Normal single entry *.entry.js -> Multiple entrypoints.

jkarttunen avatar Nov 27 '18 14:11 jkarttunen

In case you run into this problem trying to build a chrome extension (and need eg. an options.html and a index.html), here is what worked for me:

I just appended a hash to the index.html and "route" to the correct view in App.js. Something like:

  "manifest_version": 2,
  "name": "Random",
  "version": "1.0.0",
  "options_page": "index.html#options",
...
  "browser_action": {
    "default_popup": "index.html#popup"
  },```

receter avatar Dec 19 '18 15:12 receter

Happy New Year! (and soon Chinese New Year)

I've been watching this issue for a while, and I can see there's a couple workaround. Which one is the "recommended" way (as in, future proofing in line with CRA direction)?

wiradikusuma avatar Feb 02 '19 02:02 wiradikusuma

@wiradikusuma I do think there is no "recommended" way as long as the feature is not provided natively. The solution you chose, depends always on your requirement and the purpose of the app.

There are a lot of good solutions in this discussion here. Thanks to all of you!

andrmoel avatar May 07 '19 07:05 andrmoel

A different use case than what was described here before: we need (and forked to get) a second entrypoint for our service worker.

The current SW approaches (with workbox and plugins) is seriously crippled for any real service worker logic (BackgroundSync, push notifications, etc). By having an additional entrypoint you gain the same Typescript stack, code sharing, etc.

@gaearon I agree that it's a complicated use case, but the point of great tools is to be simple at first and let you access more expert-y features when you need them. I really wished there was an other way than forking, SW are an advanced feature, but fiddling with react-scripts and maintaining your fork afterwards is a whole different game.

b4stien avatar Jun 07 '19 07:06 b4stien

Since your fork is now up and running, maybe Facebook maintainers will accept a pull request?

me21 avatar Jun 07 '19 08:06 me21

Our fork is pretty much "make it work" for our use case, definitely not "make it right". There would be multiple other concerns for inclusion in a project like CRA (docs, tests, ), I'm not sure a PR with our minimal modifications would be needed.

That being said, feel free to use it, or be inspired from it, it's available at https://github.com/ouihelp/create-react-app/pull/4/files - and you can also try it with:

yarn create react-app sw-entrypoint-test-app --typescript --scripts-version @ouihelp/react-scripts

b4stien avatar Jun 09 '19 11:06 b4stien

Just wanted to post an alternate requirement for multiple entry-points that isn't the normal "User" app and "Admin" app thing.

Implementing "Silent Authentication" with an identity provider such as Auth0: https://auth0.com/docs/api-auth/tutorials/silent-authentication

You definitely don't want the silent-auth redirect loading a lot of code. But at the same time there are definitely a few things you do (or at least I do) want to share at a code level (post target, etc.) without having to go all the way to defining those shared parts as configuration or even factoring out a whole shared library.

I'm fine with ejecting being the solution to this - just wanted to mention it as a use-case of this feature for future consideration.

shorn avatar Jul 19 '19 00:07 shorn

I think it's obvious that most people want a config file at project root to overwrite specific keys of react-scripts' Webpack configuration (and leave leave the rest as they are) - without ejecting or forking the whole entity of react-scripts.

I mean... This could also be in your (Create React App team) interest, as 1/3 of all issues are related to Webpack: https://github.com/facebook/create-react-app/issues?utf8=%E2%9C%93&q=is%3Aissue+webpack

bennettdams avatar Aug 13 '19 11:08 bennettdams

We had to build an admin / portal apps and did not want to come up with two different projects as this requires extracting common components into third project, which complicates the entire stack.

As create-react-app is a single app build system, we came up with the following solution:

Assuming you have two entry points admin & portal, we simply maintain two entry point files (instead of the default index.js), so we have:

  • src/admin.js
  • src/portal.js

then we we have a simple switch-entrypoint command that does:

# replace "homepage" property in package.json
sed -i '' -e 's/\("homepage": \)\(.*\)/\1"\/${ENTRYPOINT}",/' package.json
# override index.js with the correct entry point
cp src/${ENTRYPOINT}.js src/index.js

for those who's using Makefile, here's an example

Advantages:

  • No eject
  • Simple and clean

"Disadvantages":

  • Two build folders
  • Cannot start single dev server for both projects

I personally see the "Disadvantages" as a positive thing as IMO we should treat our different entry points as isolated apps.

Thanks

asaf avatar Sep 20 '19 06:09 asaf

Here is one more example CRA with multiple entries without ejecting. Basic idea is to symlink desired entrypoint to index.js on start and build, and you can have how many you want, in a single app.

https://github.com/iamandrewluca/example-cra-multiple-entries/pull/3

Notes:

  • You can't have multiple entries started at same time
  • You can't build multiple entries at the same time
  • Each entry will have it's own build folder.
  • For PUBLIC_URL just append it before build-* command https://github.com/iamandrewluca/example-cra-multiple-entries/pull/4

iamandrewluca avatar Oct 05 '19 10:10 iamandrewluca

Looking at PR 5671 (which was closed because nobody reviewed it) seemed like small change to support output to multiple html files.

I'm admittedly new to the CRA stack. But I'm surprised to see this issue about multiple entry points limping along for over two years. Is this not a common problem, or, are most folks only building small-sized apps with this framework?

Isnt there just some sort of a "safety valve" placeholder one can add into webpack to support modifications/overrides to support something like 5671

PeteW avatar Oct 08 '19 18:10 PeteW

Does anyone have an example working for CRA v3.2.0?

leojh avatar Nov 15 '19 04:11 leojh

This package https://github.com/Derek-Hu/react-app-rewire-multiple-entry worked flawlessly for me and we have a non standard CRA setup.

leojh avatar Nov 16 '19 00:11 leojh

I successfully workaround this using dynamic import and React lazy loading of main component, might want to see in this gist

This way, while the entry point is still the unswitchable index.js, main component loaded by it can still be switched using environment variable.

benedictjohannes avatar Dec 04 '19 02:12 benedictjohannes

Just so everyone's clear, switching the entry point via environment variables or otherwise does not actually solve the problem of not having multiple entry points. With actual multiple entry points, you can optimize the bundles via webpack such that scripts are shared between bundles in an efficient way. You get none of this with only a single entry point, because webpack doesn't have the whole picture.

jednano avatar Dec 04 '19 02:12 jednano

@jedmao I get your point, I even have an example where this would be useful. We have a project where it has 5 frontend modules. Each module share same library also created internaly as living style guide. Would be great when building all 5 modules, they would share, common dependencies like, react, react-dom, bootstrap, reactstrap, our living style guide, and more dependencies.

iamandrewluca avatar Dec 16 '19 09:12 iamandrewluca

And you can do exactly that. You just need to eject and tweak your webpack configuration. It works beautifully.

jednano avatar Dec 16 '19 12:12 jednano

And you can do exactly that. You just need to eject and tweak your webpack configuration. It works beautifully.

The supposed benefit of CRA is having the easy path of React development without needing to tweak configurations. Using CRA is very streamlined in that it allows seamless upgrade of try-and-tested best practice configuration. Most boilerplate is clone-once-then-orphan from its upstream repo, that cannot be easily upgraded with NPM. Ejecting CRA and tweak sounds great, except when you have a lot of repo/app you need to maintain, each of them might need update every once in a while. Allowing customization without ejecting is opening CRA for much wider use cases. If you're suggesting ejecting, everything becomes possible but you're defeating the purpose of easy maintenance of CRA bootstrapped app. If ejecting is already desired, I think we won't be here asking for these features.

benedictjohannes avatar Dec 18 '19 21:12 benedictjohannes

I agree, but considering the complexity of just blindly adding more entry points without accounting for your specific bundling needs, which will 100% need tweaking, I think the real solution here is to make this aspect and the ability to tune bundles both configurable in a CRA.

Understanding that making CRA configurable is not yet within the design goals of CRA, ejecting is the only viable option.

jednano avatar Dec 18 '19 21:12 jednano

I recently encountered a similar scenario where I'd like multiple entries points and stumbled onto this issue. I'm glad to see it's still active and under discussion. I've been hesitant to eject, but I understand there is a lot of complexity with CRA managing user defined entry points.

My use-case is needing to load an iframe from CRA that uses a separate entry point.

Reading through the comments I was able to hack together a solution that should code split and bundle assets by combining the solution from @carruthe and @benedictjohannes.

Disclaimer I will be ejecting and not using this solution, but I wanted to go ahead and post it for others if they wanted to discuss the advantages and disadvantages of this approach. I've enjoyed reading through all of the creative solutions on this thread.

Note: This works with iframes.

By combining dynamic imports and url parsing here is the solution I have working locally without ejecting.

import React, { lazy, Suspense } from 'react';
import ReactDOM from 'react-dom';
import Parser from './parser'; // Parser is a URL parser to extract the example parameter.

// Parses the browser url for the example parameter value.
const { example } = Parser.parse();

const Example = lazy(() => import(`./Example${example || 3}`));

const App = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <Example />
  </Suspense>
);

ReactDOM.render(<App />, document.getElementById('root'));

Source code here: https://github.com/StephenEsser/example-code-splitting

Example demo here: https://stephenesser.github.io/example-code-splitting/

The url parser algorithm is from here

Dynamic Imports information can be found here.

StephenEsser avatar Dec 20 '19 22:12 StephenEsser

Hi @gaearon

Could you please share you thought on what could be the best way for doing this on CRA 3.3.0?

danvc avatar Dec 23 '19 17:12 danvc

I met the same problem, here is my way, it works :

Four steps to add multiple entry points to create-react-app

(say we add an admin.html):

1. Eject the project

npm run eject  

2. Add multiple entry to webpack.config.dev.js

entry: {
    index: [
      require.resolve('react-dev-utils/webpackHotDevClient'),
      require.resolve('./polyfills'),
      require.resolve('react-error-overlay'),
      paths.appIndexJs,
    ],
    admin:[
      require.resolve('react-dev-utils/webpackHotDevClient'),
      require.resolve('./polyfills'),
      require.resolve('react-error-overlay'),
      paths.appSrc + "/admin.js",
      ]
  },
  output: {
    path: paths.appBuild,
    pathinfo: true,
    filename: 'static/js/[name].bundle.js',
    chunkFilename: 'static/js/[name].chunk.js',
    publicPath: publicPath,
    devtoolModuleFilenameTemplate: info =>
      path.resolve(info.absoluteResourcePath),
  },

3. Modify HtmlWebpackPlugin

add a new plugin node:

    new HtmlWebpackPlugin({
      inject: true,
      chunks: ["index"],
      template: paths.appHtml,
    }),
    new HtmlWebpackPlugin({
      inject: true,
      chunks: ["admin"],
      template: paths.appHtml,
      filename: 'admin.html',
    }),

4. webpack Dev Server

rewrite urls /config/webpackDevServer.config.js:

    historyApiFallback: {
      disableDotRule: true,
      // 指明哪些路径映射到哪个html
      rewrites: [
        { from: /^\/admin.html/, to: '/build/admin.html' },
      ]
    }

detail: http://imshuai.com/create-react-app-multiple-entry-points/

When I try this approach, I'm getting the following error:

Cannot read property 'filter' of undefined

danvc avatar Dec 23 '19 18:12 danvc

@DanZeuss Customize-cra changed its configuration after upgrading to 2.x. Here is an example you can refer to the following:https://github.com/goblin-laboratory/cra-multi-page-template

xiaosongxiaosong avatar Dec 24 '19 02:12 xiaosongxiaosong

A functional example of create-react-app V3 working with multiple entry points: https://github.com/DanZeuss/create-react-app-multiple-entry-points

danvc avatar Dec 26 '19 04:12 danvc

@DanZeuss ~~can you split changes into different commits, so we can see what you changed?~~

  • ~~Initial app commit~~
  • ~~App ejected~~
  • ~~Your chagnes~~

I splitted it https://github.com/iamandrewluca/example-cra-multi-entry/pull/1/files

To check if works well with:

  • webpack-dev-server
  • service worker
  • public assets
  • PUBLIC_URL / homepage
  • build folder

iamandrewluca avatar Dec 26 '19 10:12 iamandrewluca

@DanZeuss

When I try this approach, I'm getting the following error:

Cannot read property 'filter' of undefined

the 'filter' error is caused by:
new ManifestPlugin({ // ......original code }), modify them:

      new ManifestPlugin({
        fileName: "asset-manifest.json",
        publicPath: publicPath,
        generate: (seed, files, entrypoints) => {
          const manifestFiles = files.reduce((manifest, file) => {
            manifest[file.name] = file.path;
            return manifest;
          }, seed);
          const _entrypoints = Object.values(entrypoints).reduce(
            (total, cur) => [...total, ...cur],
            []
          );
          const entrypointFiles = _entrypoints.filter(
            fileName => !fileName.endsWith(".map")
          );
          return {
            files: manifestFiles,
            entrypoints: entrypointFiles
          };
        }
      }),

and it works well.

zhanzizhen avatar Mar 13 '20 02:03 zhanzizhen

Hi, any update on this four year issue? Multiple entry-points are an absolute must in security perspective.

AliFlux avatar Apr 27 '20 11:04 AliFlux

security perspective.

What do you mean?

Maybe we need an RFC to discuss possible solutions/implementations.

iamandrewluca avatar Apr 27 '20 12:04 iamandrewluca

As @DanZeuss mentioned in #8249

My intention with this implementation is to allow the user add multiple entry points assuming that it’s going to generate separate bundles for each entry point.

Why? Because security. Let’s me introduce a scenario for you to understand my point. There is an application that is defined to work with many security best practices. One for example is to not allow the user have access to the application and all content related to it (JavaScript files, images, html, etc) without being authorized through login. The user may have access only after logged in. It’s to avoid the user have access to (included) JavaScript file/app and try to get some way to perform something the the user shouldn’t (understanding the code and injecting something, for example)

With this approach implemented, we can serve different apps in order to not have the whole application in a single file. It also avoids the user to have 2 separate (cra) applications for this purpose. This also avoid the user to eject cra and lose all benefits of it, including th futures one.

So, I hope we can reach a deal, but, independent our deal, it also needs the approvals from the reviewers, which maybe are offline for now. But I really hope to get a feedback from them.

Stay blessed

AliFlux avatar Apr 27 '20 14:04 AliFlux

Hello,

I wanted to implement multiple entry points in my application, Do we have some clear example/link which will be helpful, I am new to webpack and do not have thorough knowledge of it.

harshalpandey avatar Apr 27 '20 17:04 harshalpandey

Actually react-scripts configures bundler to support automatic code splitting, which is multiple entry points in regard to central entry. It splits bundle for chunks even when you do nothing special. You seen 0.chunk.js and so on? This is splittings for small chunks, it is already here (with some automatic splittings criteria).

If you want to provide some help to bundler to make it better understand the structure of your application, - then you can directly use asynchronous chunk loading for some components with something like react-loadable.

Multiple entry points allow you fully separate some code from anther to make two different app with different exclusive parts. Usually this is significantly different applications. For such case I use different create-react-app projects. Shared (library) code is in common project and multiple src dir with alias is used to its integration.

oklas avatar Apr 27 '20 19:04 oklas

@harshalpandey, I know this is a really long thread, so maybe you missed it in the collapsed comments, but I hope this comment is what you're looking for.

I spent a lot of time with the webpack bundle analyzer to ensure it was a highly optimized configuration; however, it does require ejecting a CRA. You might have to play with the minChunks values until the bundle analyzer shows you what you're looking for, which should be:

  1. A big vendor.js file (contains mostly node_modules).
  2. A smaller common.js file (contains mostly header, nav and footer code, shared between most pages on your site).
  3. A bunch of other smaller files, ranging from small to super tiny (should be just page-specific code at this point).

Screen Shot 2020-04-27 at 3 37 50 PM

jednano avatar Apr 27 '20 20:04 jednano

According to the image, it is noticeable to the naked eye that the largest area is occupied by the localization of moment and lodash.

This indicates an incorrect assembly. Localizations (including moment) are definitely not needed in budnle, only one is used in the application. They must be assebled separately, here is an example how to to do that with intl-webpack-plugin

I have not met a situation where lodash would be needed at all. Neither in my projects or in which I had to work. The es allows you to make a lot of things. Someones even struggle to avoid lodash. Nevertheless, if you really want to take something from lodash, you should use individual imports and not drag all existing code to the client.

Regarding everything else, based on these two examples above, it should be more interesting not how the bundle is assembled, but the app coverage. That is, how much code in the bundle really executes in the application (and recuired to be in bundle).

oklas avatar Apr 28 '20 08:04 oklas

That image is from 2015, so take it for what it's worth 😄

jednano avatar Apr 28 '20 08:04 jednano

The mentioned questions is not related to the year and bundle version. It is application configuration and with same errors it will be bundled same way today (as before).

oklas avatar Apr 28 '20 08:04 oklas

@oklas his point was to show how app bundle can be splitted, not what actually is included in bundle

This weekend I experimented with angular-cli. They have a config, where you can add multiple projects

e.g. image

For each project you can configure a lot of options

e.g image

Maybe react-scripts should start moving to use this aproach maybe, to have a config file.

iamandrewluca avatar Apr 28 '20 08:04 iamandrewluca

Hello Guys,

Thanks a lot for your reply, But I am still not clear about the implementation with react application.

Let me try to tell you the exact use case of what exactly I want to achieve in my application.

Basically I need two entry points in the application one for the mail application and another is for the admin panel.

For Ex. localhost:3000/app - it should run the main application. localhost::3000/adminApp - it should run the application which will only be having menus that are related to admin.

Please let me know what is the exact solution I can use which will resolve my problem.

if something other then multiple entry points we can do to achieve this without ejecting the application that will also be fine.

harshalpandey avatar Apr 28 '20 10:04 harshalpandey

import Loadable from 'react-loadable'
import Loading from './my-loading-component'
import {BrowserRouter as Router, Switch, Route} from "react-router-dom"

const LoadableApp = Loadable({
  loader: () => import('./my-app'),
  loading: Loading,
});

const LoadableAdmin = Loadable({
  loader: () => import('./my-admin'),
  loading: Loading,
});

const App = () => (
  <Router>
    <Switch>
      <Route path="/app"><LoadableApp/></Route>
      <Route path="/admin"><LoadableAdmin/></Route>
    </Switch>
  </Router>
)

oklas avatar Apr 28 '20 12:04 oklas

In my situation, I have my main App.js using index.html but I also have chromecast-receiver.html for Google Chromecast application, I can't figure out a way to build and bundle my Chromecast App with CRA

9jaGuy avatar Apr 29 '20 03:04 9jaGuy

@oklas Thanks for your solution.

I have one doubt/question. The solution which you have mentioned in which we are anyway using React Routes.

So after deploying build of my application on the server.

if I run my application and hit URL https://example.com it will initiate the route and then I can hit any of the URL (/app , /admin), and accordingly, it will load the related component.

But If I directly hit the URL https://example.com/app or https://example.com/admin , I think it will search for app or admin directory in my build folder and it will give me an error that

Not Found The requested URL /app was not found on this server.

Do we have any solution for this?

harshalpandey avatar Apr 29 '20 14:04 harshalpandey

@harshalpandey when having a SPA, in most of cases server should return index.html when no request handler found.

iamandrewluca avatar Apr 29 '20 15:04 iamandrewluca

You could do something funky with shell scripting. Not sure how practical this is but it will solve your problem (from how I interpret it).

Have two entry point js files, lets go with user.js and admin.js.

Have two build commands in package.json; build-user, build-admin.

When you go to run build-user, before running react-scripts build, have some shell code that copies user.js to index.js in your source code. Same for build-admin, just copy it's entry point to index.js before running the actual build command. This way if you're working on the same entry point consistently, you can still use build.

package.json

"build": "react-scripts build",
"build-user": "cp src/user.js src/index.js && react-scripts build",
"build-admin": "cp src/admin.js src/index.js && react-scripts build"

this sadly is still the best solution to avoid to eject

maybe is possible to use https://github.com/timarney/react-app-rewired/ and use some webpack plugin / configuration?

gino8080 avatar May 08 '20 12:05 gino8080

any recommendation for me?

9jaGuy avatar May 08 '20 16:05 9jaGuy