diff --git a/docs/01-getting-started/01-installation.mdx b/docs/01-getting-started/01-installation.mdx index 40121f65e3f8a..7882d271c313c 100644 --- a/docs/01-getting-started/01-installation.mdx +++ b/docs/01-getting-started/01-installation.mdx @@ -30,6 +30,7 @@ Would you like to use ESLint? No / Yes Would you like to use Tailwind CSS? No / Yes Would you like to use `src/` directory? No / Yes Would you like to use App Router? (recommended) No / Yes +Would you like to use Turbopack for `next dev`? No / Yes Would you like to customize the default import alias (@/*)? No / Yes What import alias would you like configured? @/* ``` diff --git a/docs/02-app/02-api-reference/06-create-next-app.mdx b/docs/02-app/02-api-reference/06-create-next-app.mdx index 7590067d62c2d..32775cbd6735e 100644 --- a/docs/02-app/02-api-reference/06-create-next-app.mdx +++ b/docs/02-app/02-api-reference/06-create-next-app.mdx @@ -38,6 +38,7 @@ Would you like to use ESLint? No / Yes Would you like to use Tailwind CSS? No / Yes Would you like to use `src/` directory? No / Yes Would you like to use App Router? (recommended) No / Yes +Would you like to use Turbopack for `next dev`? No / Yes Would you like to customize the default import alias (@/*)? No / Yes ``` @@ -80,6 +81,10 @@ Options: Initialize inside a `src/` directory. + --turbo + + Enable Turbopack by default for development. + --import-alias Specify import alias to use (default "@/*"). diff --git a/packages/create-next-app/README.md b/packages/create-next-app/README.md index ddb56d3577329..d6488cb8b699b 100644 --- a/packages/create-next-app/README.md +++ b/packages/create-next-app/README.md @@ -59,6 +59,10 @@ Options: Initialize inside a `src/` directory. + --turbo + + Enable Turbopack by default for development. + --import-alias Specify import alias to use (default "@/*"). diff --git a/packages/create-next-app/create-app.ts b/packages/create-next-app/create-app.ts index cbd7d2110b25d..cb4efb39e8201 100644 --- a/packages/create-next-app/create-app.ts +++ b/packages/create-next-app/create-app.ts @@ -36,6 +36,7 @@ export async function createApp({ importAlias, skipInstall, empty, + turbo, }: { appPath: string packageManager: PackageManager @@ -49,6 +50,7 @@ export async function createApp({ importAlias: string skipInstall: boolean empty: boolean + turbo: boolean }): Promise { let repoInfo: RepoInfo | undefined const mode: TemplateMode = typescript ? 'ts' : 'js' @@ -229,6 +231,7 @@ export async function createApp({ srcDir, importAlias, skipInstall, + turbo, }) } diff --git a/packages/create-next-app/index.ts b/packages/create-next-app/index.ts index 972d8237986d2..0df821e913870 100644 --- a/packages/create-next-app/index.ts +++ b/packages/create-next-app/index.ts @@ -83,6 +83,13 @@ const program = new Commander.Command(packageJson.name) ` Initialize inside a \`src/\` directory. +` + ) + .option( + '--turbo', + ` + + Enable Turbopack by default for development. ` ) .option( @@ -272,6 +279,7 @@ async function run(): Promise { importAlias: '@/*', customizeImportAlias: false, empty: false, + turbo: false, } const getPrefOrDefault = (field: string) => preferences[field] ?? defaults[field] @@ -396,6 +404,25 @@ async function run(): Promise { } } + if (!program.turbo && !process.argv.includes('--no-turbo')) { + if (ciInfo.isCI) { + program.turbo = getPrefOrDefault('turbo') + } else { + const styledTurbo = blue('Turbopack') + const { turbo } = await prompts({ + onState: onPromptState, + type: 'toggle', + name: 'turbo', + message: `Would you like to use ${styledTurbo} for ${`next dev`}?`, + initial: getPrefOrDefault('turbo'), + active: 'Yes', + inactive: 'No', + }) + program.turbo = Boolean(turbo) + preferences.turbo = Boolean(turbo) + } + } + const importAliasPattern = /^[^*"]+\/\*\s*$/ if ( typeof program.importAlias !== 'string' || @@ -455,6 +482,7 @@ async function run(): Promise { importAlias: program.importAlias, skipInstall: program.skipInstall, empty: program.empty, + turbo: program.turbo, }) } catch (reason) { if (!(reason instanceof DownloadError)) { @@ -485,6 +513,7 @@ async function run(): Promise { importAlias: program.importAlias, skipInstall: program.skipInstall, empty: program.empty, + turbo: program.turbo, }) } conf.set('preferences', preferences) diff --git a/packages/create-next-app/templates/index.ts b/packages/create-next-app/templates/index.ts index 0dc12dd0fde45..53b6fe87fab10 100644 --- a/packages/create-next-app/templates/index.ts +++ b/packages/create-next-app/templates/index.ts @@ -39,6 +39,7 @@ export const installTemplate = async ({ srcDir, importAlias, skipInstall, + turbo, }: InstallTemplateArgs) => { console.log(bold(`Using ${packageManager}.`)); @@ -174,7 +175,7 @@ export const installTemplate = async ({ version: "0.1.0", private: true, scripts: { - dev: "next dev", + dev: `next dev${turbo ? " --turbo" : ""}`, build: "next build", start: "next start", lint: "next lint", diff --git a/packages/create-next-app/templates/types.ts b/packages/create-next-app/templates/types.ts index 7d215900da89d..1837495ab8a08 100644 --- a/packages/create-next-app/templates/types.ts +++ b/packages/create-next-app/templates/types.ts @@ -29,4 +29,5 @@ export interface InstallTemplateArgs { srcDir: boolean; importAlias: string; skipInstall: boolean; + turbo: boolean; } diff --git a/test/integration/create-next-app/index.test.ts b/test/integration/create-next-app/index.test.ts index 12491062b9d9e..c55efaa918a53 100644 --- a/test/integration/create-next-app/index.test.ts +++ b/test/integration/create-next-app/index.test.ts @@ -30,6 +30,7 @@ describe.skip('create-next-app', () => { projectName, '--ts', '--app', + '--no-turbo', '--no-eslint', '--no-tailwind', '--no-src-dir', @@ -99,6 +100,7 @@ describe.skip('create-next-app', () => { projectName, '--ts', '--app', + '--no-turbo', '--no-eslint', '--no-tailwind', '--no-src-dir', diff --git a/test/integration/create-next-app/package-manager/bun.test.ts b/test/integration/create-next-app/package-manager/bun.test.ts index 45be95ab3c4d5..6529b6ab71a13 100644 --- a/test/integration/create-next-app/package-manager/bun.test.ts +++ b/test/integration/create-next-app/package-manager/bun.test.ts @@ -36,6 +36,7 @@ describe.skip('create-next-app with package manager bun', () => { '--ts', '--app', '--use-bun', + '--no-turbo', '--no-eslint', '--no-src-dir', '--no-tailwind', @@ -55,72 +56,73 @@ describe.skip('create-next-app with package manager bun', () => { }) }) }) -}) -it('should use bun when user-agent is bun', async () => { - await useTempDir(async (cwd) => { - const projectName = 'user-agent-bun' - const res = await run( - [ - projectName, - '--ts', - '--app', - '--no-eslint', - '--no-src-dir', - '--no-tailwind', - '--no-import-alias', - ], - nextInstall.installDir, - { - cwd, - env: { npm_config_user_agent: 'bun' }, - } - ) + it('should use bun when user-agent is bun', async () => { + await useTempDir(async (cwd) => { + const projectName = 'user-agent-bun' + const res = await run( + [ + projectName, + '--ts', + '--app', + '--no-turbo', + '--no-eslint', + '--no-src-dir', + '--no-tailwind', + '--no-import-alias', + ], + nextInstall.installDir, + { + cwd, + env: { npm_config_user_agent: 'bun' }, + } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) -}) -it('should use bun for --use-bun flag with example', async () => { - await useTempDir(async (cwd) => { - const projectName = 'use-bun-with-example' - const res = await run( - [projectName, '--use-bun', '--example', FULL_EXAMPLE_PATH], - nextInstall.installDir, - { cwd } - ) + it('should use bun for --use-bun flag with example', async () => { + await useTempDir(async (cwd) => { + const projectName = 'use-bun-with-example' + const res = await run( + [projectName, '--use-bun', '--example', FULL_EXAMPLE_PATH], + nextInstall.installDir, + { cwd } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) -}) -it('should use bun when user-agent is bun with example', async () => { - await useTempDir(async (cwd) => { - const projectName = 'user-agent-bun-with-example' - const res = await run( - [projectName, '--example', FULL_EXAMPLE_PATH], - nextInstall.installDir, - { - cwd, - env: { npm_config_user_agent: 'bun' }, - } - ) + it('should use bun when user-agent is bun with example', async () => { + await useTempDir(async (cwd) => { + const projectName = 'user-agent-bun-with-example' + const res = await run( + [projectName, '--example', FULL_EXAMPLE_PATH], + nextInstall.installDir, + { + cwd, + env: { npm_config_user_agent: 'bun' }, + } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) }) diff --git a/test/integration/create-next-app/package-manager/npm.test.ts b/test/integration/create-next-app/package-manager/npm.test.ts index 20babd843035e..fe87dcc11c17d 100644 --- a/test/integration/create-next-app/package-manager/npm.test.ts +++ b/test/integration/create-next-app/package-manager/npm.test.ts @@ -29,6 +29,7 @@ describe.skip('create-next-app with package manager npm', () => { '--ts', '--app', '--use-npm', + '--no-turbo', '--no-eslint', '--no-src-dir', '--no-tailwind', @@ -48,72 +49,73 @@ describe.skip('create-next-app with package manager npm', () => { }) }) }) -}) -it('should use npm when user-agent is npm', async () => { - await useTempDir(async (cwd) => { - const projectName = 'user-agent-npm' - const res = await run( - [ - projectName, - '--ts', - '--app', - '--no-eslint', - '--no-src-dir', - '--no-tailwind', - '--no-import-alias', - ], - nextInstall.installDir, - { - cwd, - env: { npm_config_user_agent: 'npm' }, - } - ) + it('should use npm when user-agent is npm', async () => { + await useTempDir(async (cwd) => { + const projectName = 'user-agent-npm' + const res = await run( + [ + projectName, + '--ts', + '--app', + '--no-turbo', + '--no-eslint', + '--no-src-dir', + '--no-tailwind', + '--no-import-alias', + ], + nextInstall.installDir, + { + cwd, + env: { npm_config_user_agent: 'npm' }, + } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) -}) -it('should use npm for --use-npm flag with example', async () => { - await useTempDir(async (cwd) => { - const projectName = 'use-npm-with-example' - const res = await run( - [projectName, '--use-npm', '--example', FULL_EXAMPLE_PATH], - nextInstall.installDir, - { cwd } - ) + it('should use npm for --use-npm flag with example', async () => { + await useTempDir(async (cwd) => { + const projectName = 'use-npm-with-example' + const res = await run( + [projectName, '--use-npm', '--example', FULL_EXAMPLE_PATH], + nextInstall.installDir, + { cwd } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) -}) -it('should use npm when user-agent is npm with example', async () => { - await useTempDir(async (cwd) => { - const projectName = 'user-agent-npm-with-example' - const res = await run( - [projectName, '--example', FULL_EXAMPLE_PATH], - nextInstall.installDir, - { - cwd, - env: { npm_config_user_agent: 'npm' }, - } - ) + it('should use npm when user-agent is npm with example', async () => { + await useTempDir(async (cwd) => { + const projectName = 'user-agent-npm-with-example' + const res = await run( + [projectName, '--example', FULL_EXAMPLE_PATH], + nextInstall.installDir, + { + cwd, + env: { npm_config_user_agent: 'npm' }, + } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) }) diff --git a/test/integration/create-next-app/package-manager/pnpm.test.ts b/test/integration/create-next-app/package-manager/pnpm.test.ts index 02112d8d87318..1a51a41836e6a 100644 --- a/test/integration/create-next-app/package-manager/pnpm.test.ts +++ b/test/integration/create-next-app/package-manager/pnpm.test.ts @@ -37,6 +37,7 @@ describe.skip('create-next-app with package manager pnpm', () => { '--ts', '--app', '--use-pnpm', + '--no-turbo', '--no-eslint', '--no-src-dir', '--no-tailwind', @@ -56,72 +57,73 @@ describe.skip('create-next-app with package manager pnpm', () => { }) }) }) -}) -it('should use pnpm when user-agent is pnpm', async () => { - await useTempDir(async (cwd) => { - const projectName = 'user-agent-pnpm' - const res = await run( - [ - projectName, - '--ts', - '--app', - '--no-eslint', - '--no-src-dir', - '--no-tailwind', - '--no-import-alias', - ], - nextInstall.installDir, - { - cwd, - env: { npm_config_user_agent: 'pnpm' }, - } - ) + it('should use pnpm when user-agent is pnpm', async () => { + await useTempDir(async (cwd) => { + const projectName = 'user-agent-pnpm' + const res = await run( + [ + projectName, + '--ts', + '--app', + '--no-turbo', + '--no-eslint', + '--no-src-dir', + '--no-tailwind', + '--no-import-alias', + ], + nextInstall.installDir, + { + cwd, + env: { npm_config_user_agent: 'pnpm' }, + } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) -}) -it('should use pnpm for --use-pnpm flag with example', async () => { - await useTempDir(async (cwd) => { - const projectName = 'use-pnpm-with-example' - const res = await run( - [projectName, '--use-pnpm', '--example', FULL_EXAMPLE_PATH], - nextInstall.installDir, - { cwd } - ) + it('should use pnpm for --use-pnpm flag with example', async () => { + await useTempDir(async (cwd) => { + const projectName = 'use-pnpm-with-example' + const res = await run( + [projectName, '--use-pnpm', '--example', FULL_EXAMPLE_PATH], + nextInstall.installDir, + { cwd } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) -}) -it('should use pnpm when user-agent is pnpm with example', async () => { - await useTempDir(async (cwd) => { - const projectName = 'user-agent-pnpm-with-example' - const res = await run( - [projectName, '--example', FULL_EXAMPLE_PATH], - nextInstall.installDir, - { - cwd, - env: { npm_config_user_agent: 'pnpm' }, - } - ) + it('should use pnpm when user-agent is pnpm with example', async () => { + await useTempDir(async (cwd) => { + const projectName = 'user-agent-pnpm-with-example' + const res = await run( + [projectName, '--example', FULL_EXAMPLE_PATH], + nextInstall.installDir, + { + cwd, + env: { npm_config_user_agent: 'pnpm' }, + } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) }) diff --git a/test/integration/create-next-app/package-manager/yarn.test.ts b/test/integration/create-next-app/package-manager/yarn.test.ts index b507a19b6d20c..4211ee23deab4 100644 --- a/test/integration/create-next-app/package-manager/yarn.test.ts +++ b/test/integration/create-next-app/package-manager/yarn.test.ts @@ -30,6 +30,7 @@ describe.skip('create-next-app with package manager yarn', () => { '--ts', '--app', '--use-yarn', + '--no-turbo', '--no-eslint', '--no-src-dir', '--no-tailwind', @@ -49,72 +50,73 @@ describe.skip('create-next-app with package manager yarn', () => { }) }) }) -}) -it('should use yarn when user-agent is yarn', async () => { - await useTempDir(async (cwd) => { - const projectName = 'user-agent-yarn' - const res = await run( - [ - projectName, - '--ts', - '--app', - '--no-eslint', - '--no-src-dir', - '--no-tailwind', - '--no-import-alias', - ], - 'canary', - { - cwd, - env: { npm_config_user_agent: 'yarn' }, - } - ) + it('should use yarn when user-agent is yarn', async () => { + await useTempDir(async (cwd) => { + const projectName = 'user-agent-yarn' + const res = await run( + [ + projectName, + '--ts', + '--app', + '--no-turbo', + '--no-eslint', + '--no-src-dir', + '--no-tailwind', + '--no-import-alias', + ], + 'canary', + { + cwd, + env: { npm_config_user_agent: 'yarn' }, + } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) -}) -it('should use yarn for --use-yarn flag with example', async () => { - await useTempDir(async (cwd) => { - const projectName = 'use-yarn-with-example' - const res = await run( - [projectName, '--use-yarn', '--example', FULL_EXAMPLE_PATH], - 'canary', - { cwd } - ) + it('should use yarn for --use-yarn flag with example', async () => { + await useTempDir(async (cwd) => { + const projectName = 'use-yarn-with-example' + const res = await run( + [projectName, '--use-yarn', '--example', FULL_EXAMPLE_PATH], + 'canary', + { cwd } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) -}) -it('should use yarn when user-agent is yarn with example', async () => { - await useTempDir(async (cwd) => { - const projectName = 'user-agent-yarn-with-example' - const res = await run( - [projectName, '--example', FULL_EXAMPLE_PATH], - 'canary', - { - cwd, - env: { npm_config_user_agent: 'yarn' }, - } - ) + it('should use yarn when user-agent is yarn with example', async () => { + await useTempDir(async (cwd) => { + const projectName = 'user-agent-yarn-with-example' + const res = await run( + [projectName, '--example', FULL_EXAMPLE_PATH], + 'canary', + { + cwd, + env: { npm_config_user_agent: 'yarn' }, + } + ) - expect(res.exitCode).toBe(0) - projectFilesShouldExist({ - cwd, - projectName, - files, + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files, + }) }) }) }) diff --git a/test/integration/create-next-app/prompts.test.ts b/test/integration/create-next-app/prompts.test.ts index b360eb3cfb8ac..07bc45dfd3d6c 100644 --- a/test/integration/create-next-app/prompts.test.ts +++ b/test/integration/create-next-app/prompts.test.ts @@ -22,6 +22,7 @@ describe.skip('create-next-app prompts', () => { '--ts', '--app', '--eslint', + '--no-turbo', '--no-src-dir', '--no-tailwind', '--no-import-alias', @@ -59,6 +60,7 @@ describe.skip('create-next-app prompts', () => { projectName, '--app', '--eslint', + '--no-turbo', '--no-tailwind', '--no-src-dir', '--no-import-alias', @@ -95,6 +97,7 @@ describe.skip('create-next-app prompts', () => { '--ts', '--app', '--eslint', + '--no-turbo', '--no-src-dir', '--no-import-alias', ], @@ -130,6 +133,7 @@ describe.skip('create-next-app prompts', () => { '--ts', '--app', '--eslint', + '--no-turbo', '--no-tailwind', '--no-src-dir', ], diff --git a/test/integration/create-next-app/templates/app.test.ts b/test/integration/create-next-app/templates/app.test.ts index 05738c7a8ad89..fc6defe293ea5 100644 --- a/test/integration/create-next-app/templates/app.test.ts +++ b/test/integration/create-next-app/templates/app.test.ts @@ -1,3 +1,4 @@ +import { join } from 'node:path' import { createNextApp, projectShouldHaveNoGitChanges, @@ -7,7 +8,7 @@ import { useTempDir, } from '../utils' -let testVersion +let testVersion: string beforeAll(async () => { if (testVersion) return // TODO: investigate moving this post publish or create deployed GH#57025 @@ -222,4 +223,32 @@ describe.skip('create-next-app --app (App Router)', () => { }) }) }) + + it('should enable turbopack dev with --turbo flag', async () => { + await useTempDir(async (cwd) => { + const projectName = 'app-turbo' + const childProcess = createNextApp( + [ + projectName, + '--ts', + '--app', + '--eslint', + '--turbo', + '--no-src-dir', + '--no-tailwind', + '--no-import-alias', + ], + { + cwd, + }, + testVersion + ) + + const exitCode = await spawnExitPromise(childProcess) + expect(exitCode).toBe(0) + const projectRoot = join(cwd, projectName) + const pkgJson = require(join(projectRoot, 'package.json')) + expect(pkgJson.scripts.dev).toBe('next dev --turbo') + }) + }) }) diff --git a/test/integration/create-next-app/templates/pages.test.ts b/test/integration/create-next-app/templates/pages.test.ts index 4fbcbf5a3cf21..e16ca49a6a057 100644 --- a/test/integration/create-next-app/templates/pages.test.ts +++ b/test/integration/create-next-app/templates/pages.test.ts @@ -1,3 +1,4 @@ +import { join } from 'node:path' import { createNextApp, projectShouldHaveNoGitChanges, @@ -7,7 +8,7 @@ import { useTempDir, } from '../utils' -let testVersion +let testVersion: string beforeAll(async () => { // TODO: investigate moving this post publish or create deployed GH#57025 // tarballs to avoid these failing while a publish is in progress @@ -232,4 +233,32 @@ describe.skip('create-next-app --no-app (Pages Router)', () => { }) }) }) + + it('should enable turbopack dev with --turbo flag', async () => { + await useTempDir(async (cwd) => { + const projectName = 'pages-turbo' + const childProcess = createNextApp( + [ + projectName, + '--ts', + '--no-app', + '--eslint', + '--turbo', + '--no-src-dir', + '--no-tailwind', + '--no-import-alias', + ], + { + cwd, + }, + testVersion + ) + + const exitCode = await spawnExitPromise(childProcess) + expect(exitCode).toBe(0) + const projectRoot = join(cwd, projectName) + const pkgJson = require(join(projectRoot, 'package.json')) + expect(pkgJson.scripts.dev).toBe('next dev --turbo') + }) + }) })