facebook / create-react-app

Set up a modern web app by running one command.
https://create-react-app.dev
MIT License
102.49k stars 26.77k forks source link

Add more entry points #1084

Open riceyeh opened 7 years ago

riceyeh commented 7 years ago

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

tbillington commented 7 years ago

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)
thien-do commented 7 years ago

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

gaearon commented 7 years ago

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

jkarttunen commented 7 years ago

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.

kulakowka commented 7 years ago

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.

gaearon commented 7 years ago

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.

tbillington commented 7 years ago

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"
jkarttunen commented 7 years ago

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

carruthe commented 7 years ago

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

juliaogris commented 7 years ago

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?

smmoosavi commented 7 years ago

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.

gaearon commented 7 years ago

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

kohver commented 7 years ago

I've implemented this as proof of concept, so you can try it already by installing spiking-react-scripts@0.9.2-fork.2 on an existed CRA project:

  1. npm uninstall --save-dev react-scripts
  2. npm install --save-dev spiking-react-scripts@0.9.2-fork.2

    To add a new entry point:

  3. Create the ./pages folder in the root of project and put in them a *.js file (for example ./pages/admin.js)
  4. npm run start (npm run build also works)
  5. 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
andymac4182 commented 7 years ago

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

Alexter-progs commented 7 years ago

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.

dan-kez commented 7 years ago

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.

miraage commented 7 years ago

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

plievone commented 7 years ago

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...

maoshuai commented 7 years ago

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/

BrunoFenzl commented 7 years ago

Thanks @maoshuai. Works perfectly!

Sowed commented 7 years ago

@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',
    }),
maoshuai commented 7 years ago

@Sowed You are right, I had amended it.

codering commented 6 years ago

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

kresli commented 6 years ago

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

elzii commented 6 years ago

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

loopmode commented 6 years ago

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.

themre commented 6 years ago

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.

loopmode commented 6 years ago

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

rmoorman commented 6 years ago

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

loopmode commented 6 years ago

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

GioLogist commented 6 years ago

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:

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

Does the job for now!

gaearon commented 6 years ago

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

remy commented 6 years ago

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?

Sowed commented 6 years ago

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

kiranps commented 6 years ago

@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
martsie commented 6 years ago

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 REACTAPP in order for them to be picked up.

loopmode commented 6 years ago

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.

bradfordlemley commented 6 years ago

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.

avorobiov commented 6 years ago

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