diff --git a/docusaurus/docs/adding-multiple-entry-points.md b/docusaurus/docs/adding-multiple-entry-points.md new file mode 100644 index 00000000000..260831adba8 --- /dev/null +++ b/docusaurus/docs/adding-multiple-entry-points.md @@ -0,0 +1,27 @@ +--- +id: adding-multiple-entry-points +title: Adding multiple entry points +--- + +Create React App comes with an embedded `index.html` and `index.js` which is shown when you're running the `app` at `http://localhost:3000`. In case you decided to add new `apps` (pages/entry point), please, follow the steps: + +## Creating files + +Please, create your `.html` and `.js` (Or TypeScript file). The embedded files come inside `public` and `src` folder. + +## Add the new app in package.json + +After creating the `app` files, you must specify it into the `appPages` inside the `package.json`. It should shape the following structure: + +```json +{ + "name": "login", + "title": "login", + "appHtml": "public/login.html", + "appIndexJs": "src/login" +} +``` + +## Accessing the new app + +Run `npm start` or `yarn start` and change the URL by referencing the new page, like `http://localhost:3000/login.html` diff --git a/docusaurus/website/sidebars.json b/docusaurus/website/sidebars.json index f51541db097..edb45d18d51 100644 --- a/docusaurus/website/sidebars.json +++ b/docusaurus/website/sidebars.json @@ -47,6 +47,7 @@ ], "Deployment": ["deployment"], "Advanced Usage": [ + "adding-multiple-entry-points", "custom-templates", "can-i-use-decorators", "pre-rendering-into-static-html-files", @@ -55,4 +56,4 @@ ], "Support": ["troubleshooting"] } -} +} \ No newline at end of file diff --git a/packages/create-react-app/createReactApp.js b/packages/create-react-app/createReactApp.js index bd60bed0d76..ee04dd734c9 100755 --- a/packages/create-react-app/createReactApp.js +++ b/packages/create-react-app/createReactApp.js @@ -240,6 +240,15 @@ function createApp( name: appName, version: '0.1.0', private: true, + // entry points + appPages: [ + { + name: 'index', + title: 'index', + appHtml: 'public/index.html', + appIndexJs: 'src/index', + }, + ], }; fs.writeFileSync( path.join(root, 'package.json'), diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index acaca7f4d48..b82314677c8 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -73,13 +73,31 @@ const resolveModule = (resolveFn, filePath) => { return resolveFn(`${filePath}.js`); }; +// Resolve appPages (aka entry points) +var appPages = require(resolveApp('package.json')).appPages; +// checks if there is at least one entry point specified +if (appPages === undefined || appPages === null || appPages.length === 0) { + console.log( + "You must defined the entry points in your package.json using the parameter 'appPages'." + ); + process.exit(1); +} else + appPages = appPages.map(ep => { + // maps each element by resolving the right path + return { + ...ep, + appHtml: resolveApp(ep.appHtml), + appIndexJs: resolveModule(resolveApp, ep.appIndexJs), + }; + }); + // config after eject: we're in ./config/ module.exports = { dotenv: resolveApp('.env'), appPath: resolveApp('.'), appBuild: resolveApp('build'), appPublic: resolveApp('public'), - appHtml: resolveApp('public/index.html'), + appPages, appIndexJs: resolveModule(resolveApp, 'src/index'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), @@ -102,6 +120,7 @@ module.exports = { appPath: resolveApp('.'), appBuild: resolveApp('build'), appPublic: resolveApp('public'), + appPages, appHtml: resolveApp('public/index.html'), appIndexJs: resolveModule(resolveApp, 'src/index'), appPackageJson: resolveApp('package.json'), @@ -138,7 +157,6 @@ if ( appPath: resolveApp('.'), appBuild: resolveOwn('../../build'), appPublic: resolveOwn(`${templatePath}/public`), - appHtml: resolveOwn(`${templatePath}/public/index.html`), appIndexJs: resolveModule(resolveOwn, `${templatePath}/src/index`), appPackageJson: resolveOwn('package.json'), appSrc: resolveOwn(`${templatePath}/src`), diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index bad4290061b..677c84b73be 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -146,18 +146,44 @@ module.exports = function(webpackEnv) { return loaders; }; - return { - mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', - // Stop compilation early in production - bail: isEnvProduction, - devtool: isEnvProduction - ? shouldUseSourceMap - ? 'source-map' - : false - : isEnvDevelopment && 'cheap-module-source-map', - // These are the "entry points" to our application. - // This means they will be the "root" imports that are included in JS bundle. - entry: [ + const htmlPlugins = []; + + paths.appPages.forEach(appPage => { + htmlPlugins.push( + new HtmlWebpackPlugin( + Object.assign( + {}, + { + inject: true, + template: appPage.appHtml, + filename: `${appPage.name}.html`, + title: appPage.title, + chunks: [appPage.name], + }, + isEnvProduction + ? { + minify: { + removeComments: true, + collapseWhitespace: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + removeStyleLinkTypeAttributes: true, + keepClosingSlash: true, + minifyJS: true, + minifyCSS: true, + minifyURLs: true, + }, + } + : undefined + ) + ) + ); + }); + + let entries = {}; + paths.appPages.forEach(appPage => { + entries[appPage.name] = [ // Include an alternative client for WebpackDevServer. A client's job is to // connect to WebpackDevServer by a socket and get notified about changes. // When you save a file, the client will either apply hot updates (in case @@ -171,11 +197,25 @@ module.exports = function(webpackEnv) { isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'), // Finally, this is your app's code: - paths.appIndexJs, + appPage.appIndexJs, // We include the app code last so that if there is a runtime error during // initialization, it doesn't blow up the WebpackDevServer client, and // changing JS code would still trigger a refresh. - ].filter(Boolean), + ].filter(Boolean); + }); + + return { + mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', + // Stop compilation early in production + bail: isEnvProduction, + devtool: isEnvProduction + ? shouldUseSourceMap + ? 'source-map' + : false + : isEnvDevelopment && 'cheap-module-source-map', + // These are the "entry points" to our application. + // This means they will be the "root" imports that are included in JS bundle. + entry: entries, output: { // The build folder. path: isEnvProduction ? paths.appBuild : undefined, @@ -185,7 +225,7 @@ module.exports = function(webpackEnv) { // In development, it does not produce real files. filename: isEnvProduction ? 'static/js/[name].[contenthash:8].js' - : isEnvDevelopment && 'static/js/bundle.js', + : isEnvDevelopment && 'static/js/[name].bundle.js', // TODO: remove this when upgrading to webpack 5 futureEmitAssets: true, // There are also additional JS chunk files if you use code splitting. @@ -270,8 +310,8 @@ module.exports = function(webpackEnv) { : false, }, cssProcessorPluginOptions: { - preset: ['default', { minifyFontValues: { removeQuotes: false } }] - } + preset: ['default', { minifyFontValues: { removeQuotes: false } }], + }, }), ], // Automatically split vendor and commons @@ -580,32 +620,9 @@ module.exports = function(webpackEnv) { ], }, plugins: [ - // Generates an `index.html` file with the