Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-cats-enable-turbopack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@gasket/plugin-nextjs": minor
---

Add opt-in Turbopack support for Gasket plugin server dependencies.
11 changes: 7 additions & 4 deletions packages/gasket-plugin-nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,13 @@ export default gasket.actions.getNextConfig();
### Next.js 16 and the Webpack bundler

This plugin requires **Next.js 16** or later and injects Webpack configuration
into your Next app. In Next.js 16, Turbopack is the default bundler, so Gasket
apps must use the `--webpack` flag when running the Next CLI. Generated apps
and scripts use `next build --webpack` and `next dev --webpack` by default.
Next.js 16 also requires **Node.js 20.9+**.
into your Next app. Generated apps and scripts use `next build --webpack` and
`next dev --webpack` by default. Apps can opt into Turbopack by setting
`TURBOPACK=1` and using the `--turbopack` Next CLI flag. In this mode, the
plugin removes its Webpack callback and externalizes loaded Gasket plugins for
the server bundle. App-specific Webpack aliases and fallbacks must still be
represented in the app's `turbopack` configuration. Next.js 16 also requires
**Node.js 20.9+**.

For general Webpack configurations, it is recommended to use features of the
Gasket [Webpack plugin], which will be merged into the Next.js configuration.
Expand Down
2 changes: 2 additions & 0 deletions packages/gasket-plugin-nextjs/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as actions from './actions.js';
import { prompt } from './prompt.js';
import create from './create.js';
import { webpackConfig } from './webpack-config.js';
import nextConfig from './next-config.js';
import express from './express.js';
import fastify from './fastify.js';
import workbox from './workbox.js';
Expand All @@ -23,6 +24,7 @@ const plugin = {
hooks: {
configure,
webpackConfig,
nextConfig,
prompt,
create,
express,
Expand Down
30 changes: 30 additions & 0 deletions packages/gasket-plugin-nextjs/lib/next-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// <reference types="@gasket/core" />

const GASKET_PLUGIN_NAME = /(^@gasket\/plugin-|^@[\w-]+\/gasket-plugin-)/;
// eslint-disable-next-line no-process-env
const processEnv = process.env;

/**
* Configure Next.js for Turbopack when explicitly enabled.
* @type {import('@gasket/core').HookHandler<'nextConfig'>}
*/
export default function nextConfig(gasket, config) {
if (processEnv.TURBOPACK !== '1') return config;

const turbopackConfig = { ...config };
delete turbopackConfig.webpack;

const pluginNames = (gasket.config.plugins ?? []).flatMap((plugin) => (
typeof plugin?.name === 'string' && GASKET_PLUGIN_NAME.test(plugin.name)
? [plugin.name]
: []
));

turbopackConfig.serverExternalPackages = Array.from(new Set([
...(config.serverExternalPackages ?? []),
'@gasket/core',
...pluginNames
]));

return turbopackConfig;
}
1 change: 1 addition & 0 deletions packages/gasket-plugin-nextjs/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('Plugin', function () {
'express',
'fastify',
'metadata',
'nextConfig',
'prompt',
'webpackConfig',
'workbox'
Expand Down
72 changes: 72 additions & 0 deletions packages/gasket-plugin-nextjs/test/next-config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { afterEach, describe, expect, it } from 'vitest';
import nextConfig from '../lib/next-config.js';

// eslint-disable-next-line no-process-env
const processEnv = process.env;
const originalTurbopack = processEnv.TURBOPACK;

describe('nextConfig', function () {
afterEach(function () {
if (typeof originalTurbopack === 'undefined') {
delete processEnv.TURBOPACK;
} else {
processEnv.TURBOPACK = originalTurbopack;
}
});

it('returns the existing config when Turbopack is disabled', function () {
delete processEnv.TURBOPACK;
const config = { webpack: function webpack() {} };

expect(nextConfig({ config: {} }, config)).toBe(config);
});

it('removes webpack configuration under Turbopack', function () {
processEnv.TURBOPACK = '1';

const result = nextConfig({ config: {} }, { webpack: function webpack() {} });

expect(result).not.toHaveProperty('webpack');
});

it('externalizes Gasket core and loaded plugins', function () {
processEnv.TURBOPACK = '1';
const gasket = {
config: {
plugins: [
{ name: '@gasket/plugin-nextjs' },
{ name: '@godaddy/gasket-plugin-uxp' },
{ name: 'local-plugin' },
{}
]
}
};

const result = nextConfig(gasket, {});

expect(result.serverExternalPackages).toEqual([
'@gasket/core',
'@gasket/plugin-nextjs',
'@godaddy/gasket-plugin-uxp'
]);
});

it('preserves and deduplicates existing server externals', function () {
processEnv.TURBOPACK = '1';
const gasket = {
config: {
plugins: [{ name: '@gasket/plugin-nextjs' }]
}
};

const result = nextConfig(gasket, {
serverExternalPackages: ['existing-package', '@gasket/core']
});

expect(result.serverExternalPackages).toEqual([
'existing-package',
'@gasket/core',
'@gasket/plugin-nextjs'
]);
});
});