Open riceyeh opened 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)
@riceyeh I think you asked this already? is there anything here different from https://github.com/facebookincubator/create-react-app/issues/1079
Can you explain your use case in more detail? While you asked this earlier, I wonder if I misunderstood your use case.
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.
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.
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.
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"
Maybe we could pass entry point to webpack instead? Then add instructions of shell scripting it.
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.
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;
}
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
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?
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.
I agree it's something we'd like to see in the future.
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:
npm uninstall --save-dev react-scripts
npm install --save-dev spiking-react-scripts@0.9.2-fork.2
To add a new entry point:
./pages
folder in the root of project and put in them a *.js
file (for example ./pages/admin.js
)npm run start
(npm run build
also works)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
@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/
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.
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.
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
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...
I met the same problem, here is my way, it works :
(say we add an admin.html):
npm run eject
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),
},
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',
}),
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/
Thanks @maoshuai. Works perfectly!
@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 You are right, I had amended it.
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
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
@maoshuai What changes might one need to make if using a library
and libraryTarget
in the webpack output
?
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.
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 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 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 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.
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:
index.js
to index-default.js
app2.js
to index.js
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
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.
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 , 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,
},
}),
...
]
@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
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.
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.
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.
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
id
of root element so for every entry point it has to be uniq
<div id="options-root"></div>
In addition to src/index.js, I have other entry points in my app. How do I add them?