Repository: vercel/next.js
Pull request: Add a webpack flag to create next app.  Ensure that if you set it or decline turbopack we set the --webpack flag on the generated project

Add a --webpack flag to create-next-app to allow selecting the webpack bundler.

Add the --webpack flag to the `next` command lines if it is set or if the turbopack prompt is declined.

This is necessary now that we have flipped the default.


Closes PACK-5634

Changed files:
filename: packages/create-next-app/create-app.ts
patch: @@ -19,7 +19,7 @@ import { getOnline } from './helpers/is-online'
 import { isWriteable } from './helpers/is-writeable'
 import { runTypegen } from './helpers/typegen'
 
-import type { TemplateMode, TemplateType } from './templates'
+import type { Bundler, TemplateMode, TemplateType } from './templates'
 import { getTemplateFile, installTemplate } from './templates'
 
 export class DownloadError extends Error {}
@@ -39,8 +39,7 @@ export async function createApp({
   skipInstall,
   empty,
   api,
-  turbopack,
-  rspack,
+  bundler,
   disableGit,
   reactCompiler,
 }: {
@@ -58,8 +57,7 @@ export async function createApp({
   skipInstall: boolean
   empty: boolean
   api?: boolean
-  turbopack: boolean
-  rspack: boolean
+  bundler: Bundler
   disableGit?: boolean
   reactCompiler: boolean
 }): Promise<void> {
@@ -252,8 +250,7 @@ export async function createApp({
       srcDir,
       importAlias,
       skipInstall,
-      turbopack,
-      rspack,
+      bundler,
       reactCompiler,
     })
   }

filename: packages/create-next-app/index.ts
patch: @@ -15,6 +15,7 @@ import { getPkgManager } from './helpers/get-pkg-manager'
 import { isFolderEmpty } from './helpers/is-folder-empty'
 import { validateNpmName } from './helpers/validate-pkg'
 import packageJson from './package.json'
+import { Bundler } from './templates'
 
 let projectPath: string = ''
 
@@ -54,8 +55,9 @@ const program = new Command(packageJson.name)
   .option('--biome', 'Initialize with Biome config.')
   .option('--app', 'Initialize as an App Router project.')
   .option('--src-dir', "Initialize inside a 'src/' directory.")
-  .option('--turbopack', 'Enable Turbopack by default for development.')
-  .option('--rspack', 'Using Rspack as the bundler')
+  .option('--turbopack', 'Enable Turbopack as the bundler.')
+  .option('--webpack', 'Enable Webpack as the bundler.')
+  .option('--rspack', 'Enable Rspack as the bundler')
   .option(
     '--import-alias <prefix/*>',
     'Specify import alias to use (default "@/*").'
@@ -276,7 +278,7 @@ async function run(): Promise<void> {
          * Depending on the prompt response, set the appropriate program flags.
          */
         opts.typescript = Boolean(typescript)
-        opts.javascript = !Boolean(typescript)
+        opts.javascript = !typescript
         preferences.typescript = Boolean(typescript)
       }
     }
@@ -429,7 +431,12 @@ async function run(): Promise<void> {
       }
     }
 
-    if (!opts.turbopack && !args.includes('--no-turbopack')) {
+    if (
+      !opts.turbopack &&
+      !args.includes('--no-turbopack') &&
+      !opts.webpack &&
+      !opts.rspack
+    ) {
       if (skipPrompt) {
         opts.turbopack = getPrefOrDefault('turbopack')
       } else {
@@ -446,6 +453,8 @@ async function run(): Promise<void> {
         opts.turbopack = Boolean(turbopack)
         preferences.turbopack = Boolean(turbopack)
       }
+      // If Turbopack is not selected, default to Webpack
+      opts.webpack = !opts.turbopack
     }
 
     const importAliasPattern = /^[^*"]+\/\*\s*$/
@@ -493,6 +502,14 @@ async function run(): Promise<void> {
     }
   }
 
+  const bundler: Bundler = opts.turbopack
+    ? Bundler.Turbopack
+    : opts.webpack
+      ? Bundler.Webpack
+      : opts.rspack
+        ? Bundler.Rspack
+        : Bundler.Turbopack
+
   try {
     await createApp({
       appPath,
@@ -509,8 +526,7 @@ async function run(): Promise<void> {
       skipInstall: opts.skipInstall,
       empty: opts.empty,
       api: opts.api,
-      turbopack: opts.turbopack,
-      rspack: opts.rspack,
+      bundler,
       disableGit: opts.disableGit,
       reactCompiler: opts.reactCompiler,
     })
@@ -544,8 +560,7 @@ async function run(): Promise<void> {
       importAlias: opts.importAlias,
       skipInstall: opts.skipInstall,
       empty: opts.empty,
-      turbopack: opts.turbopack,
-      rspack: opts.rspack,
+      bundler,
       disableGit: opts.disableGit,
       reactCompiler: opts.reactCompiler,
     })

filename: packages/create-next-app/templates/index.ts
patch: @@ -10,7 +10,7 @@ import { cyan, bold } from "picocolors";
 import { Sema } from "async-sema";
 import pkg from "../package.json";
 
-import { GetTemplateFileArgs, InstallTemplateArgs } from "./types";
+import { Bundler, GetTemplateFileArgs, InstallTemplateArgs } from "./types";
 
 // Do not rename or format. sync-react script relies on this line.
 // prettier-ignore
@@ -45,8 +45,7 @@ export const installTemplate = async ({
   srcDir,
   importAlias,
   skipInstall,
-  turbopack,
-  rspack,
+  bundler,
   reactCompiler,
 }: InstallTemplateArgs) => {
   console.log(bold(`Using ${packageManager}.`));
@@ -82,7 +81,7 @@ export const installTemplate = async ({
     },
   });
 
-  if (rspack) {
+  if (bundler === Bundler.Rspack) {
     const nextConfigFile = path.join(
       root,
       mode === "js" ? "next.config.mjs" : "next.config.ts",
@@ -207,15 +206,16 @@ export const installTemplate = async ({
 
   /** Copy the version from package.json or override for tests. */
   const version = process.env.NEXT_PRIVATE_TEST_VERSION ?? pkg.version;
+  const bundlerFlags = `${bundler === Bundler.Turbopack ? " --turbopack" : ""}${bundler === Bundler.Webpack ? " --webpack" : ""}`;
 
   /** Create a package.json for the new project and write it to disk. */
   const packageJson: any = {
     name: appName,
     version: "0.1.0",
     private: true,
     scripts: {
-      dev: `next dev${turbopack ? " --turbopack" : ""}`,
-      build: `next build${turbopack ? " --turbopack" : ""}`,
+      dev: `next dev${bundlerFlags}`,
+      build: `next build${bundlerFlags}`,
       start: "next start",
       ...(eslint && { lint: "eslint" }),
       ...(biome && { lint: "biome check", format: "biome format --write" }),
@@ -231,7 +231,7 @@ export const installTemplate = async ({
     devDependencies: {},
   };
 
-  if (rspack) {
+  if (bundler === Bundler.Rspack) {
     const NEXT_PRIVATE_TEST_VERSION = process.env.NEXT_PRIVATE_TEST_VERSION;
     if (
       NEXT_PRIVATE_TEST_VERSION &&

filename: packages/create-next-app/templates/types.ts
patch: @@ -31,7 +31,12 @@ export interface InstallTemplateArgs {
   srcDir: boolean;
   importAlias: string;
   skipInstall: boolean;
-  turbopack: boolean;
-  rspack: boolean;
+  bundler: Bundler;
   reactCompiler: boolean;
 }
+
+export enum Bundler {
+  Turbopack = "turbopack",
+  Webpack = "webpack",
+  Rspack = "rspack",
+}