diff --git a/packages/next/plugin.ts b/packages/next/plugin.ts index df1726b40d377..6b5429d3cfd6e 100644 --- a/packages/next/plugin.ts +++ b/packages/next/plugin.ts @@ -1 +1,5 @@ -export { createNodes, NextPluginOptions } from './src/plugins/plugin'; +export { + createNodes, + createNodesV2, + NextPluginOptions, +} from './src/plugins/plugin'; diff --git a/packages/next/src/generators/convert-to-inferred/convert-to-inferred.ts b/packages/next/src/generators/convert-to-inferred/convert-to-inferred.ts index a01338f37898a..f8834f799d583 100644 --- a/packages/next/src/generators/convert-to-inferred/convert-to-inferred.ts +++ b/packages/next/src/generators/convert-to-inferred/convert-to-inferred.ts @@ -1,10 +1,10 @@ import { createProjectGraphAsync, formatFiles, Tree } from '@nx/devkit'; import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; import { - migrateProjectExecutorsToPluginV1, + migrateProjectExecutorsToPlugin, NoTargetsToMigrateError, } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; -import { createNodes } from '../../plugins/plugin'; +import { createNodesV2 } from '../../plugins/plugin'; import { buildPostTargetTransformer } from './lib/build-post-target-transformer'; import { servePosTargetTransformer } from './lib/serve-post-target-tranformer'; @@ -17,11 +17,11 @@ export async function convertToInferred(tree: Tree, options: Schema) { const projectGraph = await createProjectGraphAsync(); const migrationLogs = new AggregatedLog(); - const migratedProjects = await migrateProjectExecutorsToPluginV1( + const migratedProjects = await migrateProjectExecutorsToPlugin( tree, projectGraph, '@nx/next/plugin', - createNodes, + createNodesV2, { buildTargetName: 'build', devTargetName: 'dev', diff --git a/packages/next/src/generators/init/init.ts b/packages/next/src/generators/init/init.ts index adea0c246ca0b..b4796f64b0655 100644 --- a/packages/next/src/generators/init/init.ts +++ b/packages/next/src/generators/init/init.ts @@ -7,7 +7,7 @@ import { readNxJson, createProjectGraphAsync, } from '@nx/devkit'; -import { addPluginV1 } from '@nx/devkit/src/utils/add-plugin'; +import { addPlugin } from '@nx/devkit/src/utils/add-plugin'; import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { reactDomVersion, reactVersion } from '@nx/react/src/utils/versions'; import { addGitIgnoreEntry } from '../../utils/add-gitignore-entry'; @@ -55,12 +55,12 @@ export async function nextInitGeneratorInternal( schema.addPlugin ??= addPluginDefault; if (schema.addPlugin) { - const { createNodes } = await import('../../plugins/plugin'); - await addPluginV1( + const { createNodesV2 } = await import('../../plugins/plugin'); + await addPlugin( host, await createProjectGraphAsync(), '@nx/next/plugin', - createNodes, + createNodesV2, { startTargetName: ['start', 'next:start', 'next-start'], buildTargetName: ['build', 'next:build', 'next-build'], diff --git a/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap b/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap index 38052c0234116..6224c2bbdf1c1 100644 --- a/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap +++ b/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap @@ -1,121 +1,131 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`@nx/next/plugin integrated projects should create nodes 1`] = ` -{ - "projects": { - "my-app": { - "root": "my-app", - "targets": { - "my-build": { - "cache": true, - "command": "next build", - "dependsOn": [ - "^build", - ], - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "next", +[ + [ + "my-app/next.config.js", + { + "projects": { + "my-app": { + "root": "my-app", + "targets": { + "my-build": { + "cache": true, + "command": "next build", + "dependsOn": [ + "^build", + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "next", + ], + }, + ], + "options": { + "cwd": "my-app", + "tty": false, + }, + "outputs": [ + "{workspaceRoot}/my-app/.next", + "{workspaceRoot}/my-app/.next/!(cache)", ], }, - ], - "options": { - "cwd": "my-app", - "tty": false, - }, - "outputs": [ - "{workspaceRoot}/my-app/.next", - "{workspaceRoot}/my-app/.next/!(cache)", - ], - }, - "my-serve": { - "command": "next dev", - "options": { - "cwd": "my-app", - }, - }, - "my-serve-static": { - "executor": "@nx/web:file-server", - "options": { - "buildTarget": "my-build", - "port": 3000, - "spa": false, - "staticFilePath": "{projectRoot}/out", - }, - }, - "my-start": { - "command": "next start", - "dependsOn": [ - "my-build", - ], - "options": { - "cwd": "my-app", + "my-serve": { + "command": "next dev", + "options": { + "cwd": "my-app", + }, + }, + "my-serve-static": { + "executor": "@nx/web:file-server", + "options": { + "buildTarget": "my-build", + "port": 3000, + "spa": false, + "staticFilePath": "{projectRoot}/out", + }, + }, + "my-start": { + "command": "next start", + "dependsOn": [ + "my-build", + ], + "options": { + "cwd": "my-app", + }, + }, }, }, }, }, - }, -} + ], +] `; exports[`@nx/next/plugin root projects should create nodes 1`] = ` -{ - "projects": { - ".": { - "root": ".", - "targets": { - "build": { - "cache": true, - "command": "next build", - "dependsOn": [ - "^build", - ], - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "next", +[ + [ + "next.config.js", + { + "projects": { + ".": { + "root": ".", + "targets": { + "build": { + "cache": true, + "command": "next build", + "dependsOn": [ + "^build", + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "next", + ], + }, + ], + "options": { + "cwd": ".", + "tty": false, + }, + "outputs": [ + "{projectRoot}/.next", + "{projectRoot}/.next/!(cache)", ], }, - ], - "options": { - "cwd": ".", - "tty": false, - }, - "outputs": [ - "{projectRoot}/.next", - "{projectRoot}/.next/!(cache)", - ], - }, - "dev": { - "command": "next dev", - "options": { - "cwd": ".", - }, - }, - "serve-static": { - "executor": "@nx/web:file-server", - "options": { - "buildTarget": "build", - "port": 3000, - "spa": false, - "staticFilePath": "{projectRoot}/out", - }, - }, - "start": { - "command": "next start", - "dependsOn": [ - "build", - ], - "options": { - "cwd": ".", + "dev": { + "command": "next dev", + "options": { + "cwd": ".", + }, + }, + "serve-static": { + "executor": "@nx/web:file-server", + "options": { + "buildTarget": "build", + "port": 3000, + "spa": false, + "staticFilePath": "{projectRoot}/out", + }, + }, + "start": { + "command": "next start", + "dependsOn": [ + "build", + ], + "options": { + "cwd": ".", + }, + }, }, }, }, }, - }, -} + ], +] `; diff --git a/packages/next/src/plugins/plugin.spec.ts b/packages/next/src/plugins/plugin.spec.ts index 44bc72d19e795..9fecb15a5c84b 100644 --- a/packages/next/src/plugins/plugin.spec.ts +++ b/packages/next/src/plugins/plugin.spec.ts @@ -1,11 +1,11 @@ import { CreateNodesContext } from '@nx/devkit'; import type { NextConfig } from 'next'; -import { createNodes } from './plugin'; +import { createNodesV2 } from './plugin'; import { TempFs } from '@nx/devkit/internal-testing-utils'; describe('@nx/next/plugin', () => { - let createNodesFunction = createNodes[1]; + let createNodesFunction = createNodesV2[1]; let context: CreateNodesContext; describe('root projects', () => { @@ -30,7 +30,7 @@ describe('@nx/next/plugin', () => { const nextConfigPath = 'next.config.js'; mockNextConfig(nextConfigPath, {}); const nodes = await createNodesFunction( - nextConfigPath, + [nextConfigPath], { buildTargetName: 'build', devTargetName: 'dev', @@ -71,7 +71,7 @@ describe('@nx/next/plugin', () => { it('should create nodes', async () => { mockNextConfig('my-app/next.config.js', {}); const nodes = await createNodesFunction( - 'my-app/next.config.js', + ['my-app/next.config.js'], { buildTargetName: 'my-build', devTargetName: 'my-serve', diff --git a/packages/next/src/plugins/plugin.ts b/packages/next/src/plugins/plugin.ts index 396b24b15d0c6..aa61b29d5e037 100644 --- a/packages/next/src/plugins/plugin.ts +++ b/packages/next/src/plugins/plugin.ts @@ -1,12 +1,15 @@ import { CreateDependencies, CreateNodes, + CreateNodesV2, CreateNodesContext, detectPackageManager, NxJsonConfiguration, readJsonFile, TargetConfiguration, writeJsonFile, + createNodesFromFiles, + logger, } from '@nx/devkit'; import { dirname, join } from 'path'; @@ -17,6 +20,7 @@ import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; import { getLockFileName } from '@nx/js'; import { loadConfigFile } from '@nx/devkit/src/utils/config-utils'; +import { hashObject } from 'nx/src/devkit-internals'; export interface NextPluginOptions { buildTargetName?: string; @@ -25,69 +29,124 @@ export interface NextPluginOptions { serveStaticTargetName?: string; } -const cachePath = join(workspaceDataDirectory, 'next.hash'); -const targetsCache = readTargetsCache(); +const nextConfigBlob = '**/next.config.{js,cjs,mjs}'; -function readTargetsCache(): Record< - string, - Record -> { +function readTargetsCache( + cachePath: string +): Record>> { return existsSync(cachePath) ? readJsonFile(cachePath) : {}; } -function writeTargetsToCache() { - const oldCache = readTargetsCache(); +function writeTargetsToCache( + cachePath: string, + targetsCache: Record> +) { + const oldCache = readTargetsCache(cachePath); writeJsonFile(cachePath, { ...oldCache, ...targetsCache, }); } +/** + * @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'. + */ export const createDependencies: CreateDependencies = () => { - writeTargetsToCache(); return []; }; -export const createNodes: CreateNodes = [ - '**/next.config.{js,cjs,mjs}', - async (configFilePath, options, context) => { - const projectRoot = dirname(configFilePath); - - // Do not create a project if package.json and project.json isn't there. - const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); - if ( - !siblingFiles.includes('package.json') && - !siblingFiles.includes('project.json') - ) { - return {}; +export const createNodesV2: CreateNodesV2 = [ + nextConfigBlob, + async (configFiles, options, context) => { + const optionsHash = hashObject(options); + const cachePath = join(workspaceDataDirectory, `next-${optionsHash}.json`); + const targetsCache = readTargetsCache(cachePath); + + try { + return await createNodesFromFiles( + (configFile, options, context) => + createNodesInternal(configFile, options, context, targetsCache), + configFiles, + options, + context + ); + } finally { + writeTargetsToCache(cachePath, targetsCache); } - options = normalizeOptions(options); + }, +]; - const hash = await calculateHashForCreateNodes( - projectRoot, - options, - context, - [getLockFileName(detectPackageManager(context.workspaceRoot))] +/** + * @deprecated This is replaced with {@link createNodesV2}. Update your plugin to export its own `createNodesV2` function that wraps this one instead. + * This function will change to the v2 function in Nx 21. + */ +export const createNodes: CreateNodes = [ + nextConfigBlob, + async (configFilePath, options, context) => { + logger.warn( + '`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 21, this will change to the createNodesV2 API.' ); - targetsCache[hash] ??= await buildNextTargets( + const optionsHash = hashObject(options); + const cachePath = join(workspaceDataDirectory, `next-${optionsHash}.json`); + const targetsCache = readTargetsCache(cachePath); + + const result = await createNodesInternal( configFilePath, - projectRoot, options, - context + context, + targetsCache ); - - return { - projects: { - [projectRoot]: { - root: projectRoot, - targets: targetsCache[hash], - }, - }, - }; + writeTargetsToCache(cachePath, targetsCache); + return result; }, ]; +async function createNodesInternal( + configFilePath: string, + options: NextPluginOptions, + context: CreateNodesContext, + targetsCache: Record< + string, + Record> + > +) { + const projectRoot = dirname(configFilePath); + + // Do not create a project if package.json and project.json isn't there. + const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); + if ( + !siblingFiles.includes('package.json') && + !siblingFiles.includes('project.json') + ) { + return {}; + } + options = normalizeOptions(options); + + const hash = await calculateHashForCreateNodes( + projectRoot, + options, + context, + [getLockFileName(detectPackageManager(context.workspaceRoot))] + ); + + targetsCache[hash] ??= await buildNextTargets( + configFilePath, + projectRoot, + options, + context + ); + + return { + projects: { + [projectRoot]: { + root: projectRoot, + targets: targetsCache[hash], + }, + }, + }; +} + async function buildNextTargets( nextConfigPath: string, projectRoot: string,