dilanx / craco

Create React App Configuration Override, an easy and comprehensible configuration layer for Create React App.
https://craco.js.org
Apache License 2.0
7.42k stars 501 forks source link

Multiple entry points #298

Open thinkerytim opened 3 years ago

thinkerytim commented 3 years ago

Is it possible to use craco to set multiple entry points?

Similar to

module.exports = {   
          devtool: "source-map",
          entry: {
            app: ["./src/index.js"],
            silentRenew: ["./silent_renew/index.js"]
patricklafrance commented 3 years ago

I doubt it will work.

ivanjeremic commented 3 years ago

Does craco in any way support multiple entries?

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

bkrausz commented 2 years ago

For those getting here from Google, I believe this config works to give you multiple entrypoints. It was ported from the much-appreciated react-app-rewire-multiple-entry.

const path = require('path');

const rewireEntries = [
  {
    name: 'background',
    entry: path.resolve(__dirname, './src/background/index.ts'),
    template: path.resolve(__dirname, './src/background/index.html'),
    outPath: 'background.html',
  },
  {
    name: 'hud',
    entry: path.resolve(__dirname, './src/hud/index.tsx'),
    template: path.resolve(__dirname, './src/hud/index.html'),
    outPath: 'hud.html',
  },
];

const defaultEntryName = 'main';

const appIndexes = ['js', 'tsx', 'ts', 'jsx'].map((ext) =>
  path.resolve(__dirname, `src/index.${ext}`)
);

function webpackMultipleEntries(config) {
  // Multiple Entry JS
  const defaultEntryHTMLPlugin = config.plugins.filter((plugin) => {
    return plugin.constructor.name === 'HtmlWebpackPlugin';
  })[0];
  defaultEntryHTMLPlugin.options.chunks = [defaultEntryName];

  // config.entry is not an array in Create React App 4
  if (!Array.isArray(config.entry)) {
    config.entry = [config.entry];
  }

  // If there is only one entry file then it should not be necessary for the rest of the entries
  const necessaryEntry =
    config.entry.length === 1
      ? []
      : config.entry.filter((file) => !appIndexes.includes(file));
  const multipleEntry = {};
  multipleEntry[defaultEntryName] = config.entry;

  rewireEntries.forEach((entry) => {
    multipleEntry[entry.name] = necessaryEntry.concat(entry.entry);
    // Multiple Entry HTML Plugin
    config.plugins.unshift(
      new defaultEntryHTMLPlugin.constructor(
        Object.assign({}, defaultEntryHTMLPlugin.options, {
          filename: entry.outPath,
          template: entry.template,
          chunks: [entry.name],
        })
      )
    );
  });
  config.entry = multipleEntry;

  // Multiple Entry Output File
  let names = config.output.filename.split('/').reverse();

  if (names[0].indexOf('[name]') === -1) {
    names[0] = '[name].' + names[0];
    config.output.filename = names.reverse().join('/');
  }

  return config;
}

module.exports = {
  webpack: {
    configure: webpackMultipleEntries,
  },
};
helsonxiao commented 2 years ago

I created a multi entry demo with craco and react-app-rewired. This demo project is simply shifted from react-app-rewired into craco accroding to bkrausz's comment.

Note: This method doesn't work with react-scripts@5.x now. I've tested it with react-scripts@5.0.0. Here's a workaround.

const path = require('path');

const rewireEntries = [
  {
    name: 'albuminfo',
    entry: path.resolve(__dirname, './src/albuminfo/index.tsx'),
    template: path.resolve(__dirname, './src/albuminfo/index.html'),
    outPath: 'albuminfo/index.html',
  },
  {
    name: 'sound',
    entry: path.resolve(__dirname, './src/sound/index.tsx'),
    template: path.resolve(__dirname, './src/sound/index.html'),
    outPath: 'sound/index.html',
  },
];

const defaultEntryName = 'main';

const appIndexes = ['js', 'tsx', 'ts', 'jsx'].map((ext) =>
  path.resolve(__dirname, `src/index.${ext}`)
);

function webpackMultipleEntries(config) {
  // Multiple Entry JS
  const defaultEntryHTMLPlugin = config.plugins.filter((plugin) => {
    return plugin.constructor.name === 'HtmlWebpackPlugin';
  })[0];
  defaultEntryHTMLPlugin.userOptions.chunks = [defaultEntryName];

  // config.entry is not an array in Create React App 4
  if (!Array.isArray(config.entry)) {
    config.entry = [config.entry];
  }

  // If there is only one entry file then it should not be necessary for the rest of the entries
  const necessaryEntry =
    config.entry.length === 1
      ? []
      : config.entry.filter((file) => !appIndexes.includes(file));
  const multipleEntry = {};
  multipleEntry[defaultEntryName] = config.entry;

  rewireEntries.forEach((entry) => {
    multipleEntry[entry.name] = necessaryEntry.concat(entry.entry);
    // Multiple Entry HTML Plugin
    config.plugins.unshift(
      new defaultEntryHTMLPlugin.constructor(
        Object.assign({}, defaultEntryHTMLPlugin.userOptions, {
          filename: entry.outPath,
          template: entry.template,
          chunks: [entry.name],
        })
      )
    );
  });
  config.entry = multipleEntry;

  // Multiple Entry Output File
  let names = config.output.filename.split('/').reverse();

  if (names[0].indexOf('[name]') === -1) {
    names[0] = '[name].' + names[0];
    config.output.filename = names.reverse().join('/');
  }

  return config;
}

module.exports = {
  webpack: {
    configure: webpackMultipleEntries,
  },
};
dilanx commented 2 years ago

It looks like the demo by @helsonxiao works as of v7.0.0-alpha.3 (probably because the dependencies for craco have been updated, specifically react-scripts) but maybe we can build this functionality directly into craco :eyes:

ivanjeremic commented 2 years ago

For those still searching for a solution just switch to Vite.js and all problems are solved.

brunano21 commented 2 years ago

@dilanx I'm facing an issue where the entry points work fine when building the app (craco build) but not when running the app locally (craco start). For some reason the webpack-web-server is hanging forever and not serving any files.

I face the same issue if I set configure.optimization.runtimeChunk to true or multiple - it builds fine, but it hangs when trying to serve the app.

I'm using CRA 5 and craco@7.0.0-alpha.7

dilanx commented 2 years ago

@brunano21 weird. Could you share your craco config?

brunano21 commented 2 years ago

Sure thing!

{
        webpack: {
            configure: (config, { env, paths }) => {
                // This makes craco (/webpack-dev-server) hanging forever if running craco start. Works fine with craco build.
                config.optimization.runtimeChunk = 'single';

                config.module.rules.unshift({
                    test: /\.js(x)?$/,
                    resolve: {
                        fullySpecified: false, // disable the behavior
                    },
                });

                // ATTEMPT #1
                // config.entry = {
                //     main: config.entry,
                //     vendor: './src/vendor.js',
                // };

                // ATTEMPT #2
                config.entry = {
                    main: config.entry,
                    vendor: {
                        import: './src/vendor.js',
                        filename: '[name][ext]',
                        // chunkLoading: false,
                        // asyncChunks: false,
                    },
                };

                return config;
            },
        },

        style: {
            css: {
                loaderOptions: () =>
                    // Prevents Webpack from trying to load URLs as modules
                    // which is incompatible with public content.
                    ({ url: false }),
            },
        },

        jest: {
            configure: (config) => {
                // some stuff here
                return config;
            },
        },

        devServer: {
            // NOTE: I have verified that even without a devServer configuration, the issue persists.
            setupMiddlewares: (middlewares, devServer) => {
                // some stuff here

                return middlewares;
            },
            onListening(devServer) {
                // some stuff here
            },
        },
    };

Console output:

> BROWSER=none PORT=3005 craco start

(node:54736) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:54736) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
Starting the development server... 
dilanx commented 2 years ago

@brunano21 okay I think I figured it out. It looks like by default in the dev environment, CRA compiles all of the entries into a single bundle, which is an issue because vendor ends up overwriting main which might be causing the hang. I was reading about entry points in the webpack docs.

Adding this to your webpack configure function in your craco config fixed it for me:

if (env === 'development') {
    config.output = {
        ...config.output,
        filename: 'static/js/[name].bundle.js',
    };
}

I console logged the default output and the filename was static/js/bundle.js (no reference to the name of the entry), so I think that's why adding the name makes it work.

brunano21 commented 2 years ago

@dilanx that did the trick, indeed! Thanks a lot for the quick turnaround. As a side effect, that also fixed the issue with the optimization.runtimeChunk = 'multiple' which was causing webpack-dev-server to hang indefinitely.

ivanjeremic commented 2 years ago

@brunano21

There is now Vite.js I would use it over CRACO any day.

sbo0149 commented 1 year ago

@brunano21 okay I think I figured it out. It looks like by default in the dev environment, CRA compiles all of the entries into a single bundle, which is an issue because vendor ends up overwriting main which might be causing the hang. I was reading about entry points in the webpack docs.

Adding this to your webpack configure function in your craco config fixed it for me:

if (env === 'development') {
    config.output = {
        ...config.output,
        filename: 'static/js/[name].bundle.js',
    };
}

I console logged the default output and the filename was static/js/bundle.js (no reference to the name of the entry), so I think that's why adding the name makes it work.

It fixed for me

webdo6263 commented 1 year ago

The above point led me to this as well. By having atleast one "main" as an entry point also we could resolve this but this approach might not be as expected for many

entry: {
  main: ...,
  entry1: ....,
  entry2: ....
}
ErikBrendel commented 1 year ago

In my use case, I wanted to keep a simple single-page react application, but additionally compile and provide a few statically available script files. This thread helped me in figuring the details out, and here is my final craco.config.ts, in case it helps anybody else (be sure to update CRA to version 5 first, so you have webpack 5 and craco 7):

const path = require('path');

type RewireEntry = {
    name: string
    entry: string
    outPath: string
}
const rewireEntries: RewireEntry[] = [
  {
    name: 'my-static-scripts',
    entry: path.resolve(__dirname, './src/static/my-static-scripts.ts'),
    outPath: 'static/my-static-scripts.js',
  },
];

const defaultEntryName = 'main';

/** adapted from: https://github.com/dilanx/craco/issues/298#issue-906476295 */
function webpackMultipleEntries(config) {
  const defaultEntryHTMLPlugin = config.plugins.filter((plugin) => plugin.constructor.name === 'HtmlWebpackPlugin')[0];
  defaultEntryHTMLPlugin.userOptions.chunks = [defaultEntryName];

  if (!Array.isArray(config.entry)) {
    config.entry = [config.entry];
  }

  const multipleEntry = {};
  multipleEntry[defaultEntryName] = config.entry;

  // see https://webpack.js.org/concepts/entry-points/#entrydescription-object
  for (const entry of rewireEntries) {
    multipleEntry[entry.name] = {
      import: entry.entry,
      filename: entry.outPath,
      runtime: false,
    };
  }
  config.entry = multipleEntry;

  // Multiple Entry Output File
  const names = config.output.filename.split('/').reverse();
  if (names[0].indexOf('[name]') === -1) {
    names[0] = `[name].${names[0]}`;
    config.output.filename = names.reverse().join('/');
  }

  return config;
}

module.exports = {
  webpack: {
    configure: webpackMultipleEntries,
  },
};
export {};
renegare commented 6 months ago

Really grateful for the work here! saved me ~6months of trauma lol.

I wrote my own version based of the comments here which I hope makes it even easier for anyone finding this thread in desperation:

// craco.config.ts

import { CracoConfig, WebpackContext } from '@craco/types';
import { Configuration as WebpackConfiguration } from 'webpack';

const config: CracoConfig = {
  webpack: {
    configure: (webpackConfig, { env, paths }) => {
      return getMultiEntryWebpackConfig(
        {
          main: './src/index.tsx',
          anotherEntry: './src/another-entry.tsx',
        },
        webpackConfig,
        { env, paths }
      );
    },
  },
};

const getMultiEntryWebpackConfig = (
  entries: { main: string; [name: string]: string },
  config: WebpackConfiguration,
  { env }: WebpackContext
): WebpackConfiguration => {
  const defaultHTMLPlugin = config.plugins!.find((plugin) => {
    return plugin?.constructor.name === 'HtmlWebpackPlugin';
  });

  if(!defaultHTMLPlugin) {
    throw new Error('HtmlWebpackPlugin not found!');
  }

  const plugins = config.plugins!.filter(plugin => plugin !== defaultHTMLPlugin);

  plugins.push(...Object.entries(entries).map(([entryName, entry]) => {
    const filename = `${entryName === 'main' ? 'index' : entryName}.html`
    // @ts-ignore
    return new defaultHTMLPlugin.constructor(
      // @ts-ignore
      Object.assign({}, defaultHTMLPlugin.userOptions, {
        filename,
        // @ts-ignore
        template: defaultHTMLPlugin.userOptions.template,
        chunks: [entryName],
      })
    )
  }));

  config.entry = entries;
  config.plugins = plugins;

  if (env === 'development') {
    config.output = {
        ...config.output,
        filename: 'static/js/[name].bundle.js',
    };
}

  return config;
};
export default config;

Note: