Create React App V2 - Multiple entry points

后端 未结 3 1207
无人共我
无人共我 2021-02-08 03:21

I\'m trying to build a React app with 2 entry points, one for the App and one for the Admin panel.

I\'m starting with Create React App V2 and following this gitHub issue

相关标签:
3条回答
  • 2021-02-08 03:56

    This one is working for the latest react version v2 but it only works for the build(npm run build); running npm start in react folder (client) would have a bug

    https://github.com/chiKaRau/git-reactv2-express-multiple-entry-point

    other sources http://houserqu.com/2018/04/26/create-react-app-%E9%85%8D%E7%BD%AE%E5%A4%9A%E9%A1%B5%E9%9D%A2/

    https://www.jianshu.com/p/951287ce12c0

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

    0 讨论(0)
  • 2021-02-08 03:58

    I know it's a delayed answer, but just for future searches, the steps are:

    1. Eject (yarn eject)
    2. Edit paths.js and add the second entry point html file under the entry for appHtml
    appAdminHtml: resolveApp('public/admin.html'),
    
    1. Update entry inside webpack.config.js to include one entry per entry point.
    entry: {
      index: [
        isEnvDevelopment &&
        require.resolve('react-dev-utils/webpackHotDevClient'),
        paths.appIndexJs,
      ].filter(Boolean),
      admin: [
        isEnvDevelopment &&
        require.resolve('react-dev-utils/webpackHotDevClient'),
        paths.appSrc + '/admin/index.js',
      ].filter(Boolean)
    },
    
    1. Change the generated output JS file to the name of the entry (inside webpack.config.js)
    output: {
      path: isEnvProduction ? paths.appBuild : undefined,
      pathinfo: isEnvDevelopment,
      // This is the important entry
      filename: isEnvProduction
        ? 'static/js/[name].[contenthash:8].js'
        : isEnvDevelopment && 'static/js/[name].bundle.js',
      futureEmitAssets: true,
      chunkFilename: isEnvProduction
        ? 'static/js/[name].[contenthash:8].chunk.js'
        : isEnvDevelopment && 'static/js/[name].chunk.js',
      publicPath: publicPath,
      devtoolModuleFilenameTemplate: isEnvProduction
        ? info =>
            path
              .relative(paths.appSrc, info.absoluteResourcePath)
              .replace(/\\/g, '/')
        : isEnvDevelopment &&
          (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
      jsonpFunction: `webpackJsonp${appPackageJson.name}`,
      globalObject: 'this',
    },
    
    1. Update the plugins to generate the second file with the injected js script (also inside webpack.config.js).
    // Generates an `index.html` file with the <script> injected.
    new HtmlWebpackPlugin(
      Object.assign(
        {},
        {
          inject: true,
          chunks: ['index'],
          template: paths.appHtml,
          filename: 'index.html'
        },
        isEnvProduction
          ? {
              minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeRedundantAttributes: true,
                useShortDoctype: true,
                removeEmptyAttributes: true,
                removeStyleLinkTypeAttributes: true,
                keepClosingSlash: true,
                minifyJS: true,
                minifyCSS: true,
                minifyURLs: true,
              },
            }
          : undefined
      )
    ),
    // Generates an `admin.html` file with the <script> injected.
    new HtmlWebpackPlugin(
      Object.assign(
        {},
        {
          inject: true,
          chunks: ['admin'],
          template: paths.appAdminHtml,
          filename: 'admin.html',
        },
        isEnvProduction
          ? {
              minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeRedundantAttributes: true,
                useShortDoctype: true,
                removeEmptyAttributes: true,
                removeStyleLinkTypeAttributes: true,
                keepClosingSlash: true,
                minifyJS: true,
                minifyCSS: true,
                minifyURLs: true,
              },
            }
          : undefined
      )
    ),
    
    1. Update the ManifestPlugin configuration to include the new entry point (also insidewebpack.config.js`):
    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);
        let entrypointFiles = [];
        for (let [entryFile, fileName] of Object.entries(entrypoints)) {
          let notMapFiles = fileName.filter(fileName => !fileName.endsWith('.map'));
          entrypointFiles = entrypointFiles.concat(notMapFiles);
        };
    
        return {
          files: manifestFiles,
          entrypoints: entrypointFiles,
        };
      },
    }),
    
    1. Update your server (both dev and prod) to rewrite paths.
      • For the dev server, you need to update webpackDevServer.config.js.
    historyApiFallback: {
      disableDotRule: true,
      verbose: true,
      rewrites: [
        { from: /^\/admin/, to: '/admin.html' },
      ]
    },
    
    

    Since Prod server settings can be quite different, I'll let you figure it out.

    This post describes everything in more detail.

    0 讨论(0)
  • 2021-02-08 04:02

    I realised that setting filename in HTMLWebpackPlugin to appPublic or adminPublic was incorrect and it should be app/index.html admin/index.html.

    However where I would like 2 separate folders in the build folder, one for the app and the other for the admin app, using this method requires more complexity because there is no entry variable in webpack that you can use to set the destination path. For example I would need to be able to do something like [entry]/static/js/[name].[contenthash:8].chunk.js. I think one way to do this would be to use Webpack MultiCompiler.

    However rather than doing this this I've passed the entry point as an environment variable in package.json, adding REACT_APP_ENTRY= like so:

      "scripts": {
        "start-app": "REACT_APP_ENTRY=app node scripts/start.js",
        "build-app": "REACT_APP_ENTRY=app node scripts/build.js",
        "start-admin": "REACT_APP_ENTRY=admin node scripts/start.js",
        "build-admin": "REACT_APP_ENTRY=admin node scripts/build.js",
        "test": "node scripts/test.js"
      },
    

    In start.js I added const isApp = process.env.REACT_APP_ENTRY === 'app'; at the top:

    'use strict';
    
    process.env.BABEL_ENV = 'development';
    process.env.NODE_ENV = 'development';
    
    const isApp = process.env.REACT_APP_ENTRY === 'app';
    

    And updated where the port is being set, this is so I can run both development servers at the same time without a clash:

    const DEFAULT_PORT = parseInt(process.env.PORT, 10) || (isApp ? 3000 : 3001);
    const HOST = process.env.HOST || '0.0.0.0';
    

    Then at the top of paths.js add const isApp = process.env.REACT_APP_ENTRY === 'app';:

    const envPublicUrl = process.env.PUBLIC_URL;
    const isApp = process.env.REACT_APP_ENTRY === 'app';
    

    And finally update the paths depending on the env variable set:

    module.exports = {
      dotenv: resolveApp('.env'),
      appPath: resolveApp('.'),
      appBuild: isApp ? resolveApp('build/app') : resolveApp('build/admin'),
      appPublic: isApp ? resolveApp('public/app') : resolveApp('public/admin'),
      appHtml: isApp ? resolveApp('public/app/index.html') : resolveApp('public/admin/index.html'),
      appIndexJs: isApp ? resolveModule(resolveApp, 'src/app') : resolveModule(resolveApp, 'src/admin'),
      appPackageJson: resolveApp('package.json'),
      appSrc: resolveApp('src'),
      appTsConfig: resolveApp('tsconfig.json'),
      yarnLockFile: resolveApp('yarn.lock'),
      testsSetup: resolveModule(resolveApp, 'src/setupTests'),
      proxySetup: resolveApp('src/setupProxy.js'),
      appNodeModules: resolveApp('node_modules'),
      publicUrl: getPublicUrl(resolveApp('package.json')),
      servedPath: getServedPath(resolveApp('package.json')),
    };
    

    I think this method as well as being far simpler is superior for this use case as it allows the flexibility to compile only the app or only the admin rather than forcing you to compile both when only one has been changed. I can run both yarn start-app and yarn start-admin at the same time with the separate apps running on different ports.

    0 讨论(0)
提交回复
热议问题