UNPKG

@nx/next

Version:

The Next.js plugin for Nx contains executors and generators for managing Next.js applications and libraries within an Nx workspace. It provides: - Scaffolding for creating, building, serving, linting, and testing Next.js applications. - Integration wit

171 lines (170 loc) 8.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createNextConfigFile = createNextConfigFile; exports.getWithNxContent = getWithNxContent; exports.findNextConfigPath = findNextConfigPath; exports.getRelativeFilesToCopy = getRelativeFilesToCopy; exports.getRelativeImports = getRelativeImports; exports.ensureFileExtensions = ensureFileExtensions; const devkit_1 = require("@nx/devkit"); const ts = require("typescript"); const node_fs_1 = require("node:fs"); const path_1 = require("path"); const js_1 = require("@nx/js"); function createNextConfigFile(options, context) { // Don't overwrite the next.config.js file if output path is the same as the source path. if (options.outputPath.replace(/\/$/, '') === context.projectGraph.nodes[context.projectName].data.root) { return; } const projectRoot = context.projectGraph.nodes[context.projectName].data.root; const configRelativeToProjectRoot = findNextConfigPath(projectRoot, // If user passed a config then it is relative to the workspace root, need to normalize it to be relative to the project root. options.nextConfig ? (0, path_1.relative)(projectRoot, options.nextConfig) : undefined); const configAbsolutePath = (0, path_1.join)(projectRoot, configRelativeToProjectRoot); if (!(0, node_fs_1.existsSync)(configAbsolutePath)) { throw new Error('next.config.js not found'); } // Copy config file and our `.nx-helpers` folder to remove dependency on @nrwl/next for production build. const helpersPath = (0, path_1.join)(options.outputPath, '.nx-helpers'); (0, node_fs_1.mkdirSync)(helpersPath, { recursive: true }); (0, node_fs_1.copyFileSync)((0, path_1.join)(__dirname, '../../../utils/compose-plugins.js'), (0, path_1.join)(helpersPath, 'compose-plugins.js')); (0, node_fs_1.writeFileSync)((0, path_1.join)(helpersPath, 'with-nx.js'), getWithNxContent()); (0, node_fs_1.writeFileSync)((0, path_1.join)(helpersPath, 'compiled.js'), ` const withNx = require('./with-nx'); module.exports = withNx; module.exports.withNx = withNx; module.exports.composePlugins = require('./compose-plugins').composePlugins; `); (0, node_fs_1.writeFileSync)((0, path_1.join)(options.outputPath, configRelativeToProjectRoot), (0, node_fs_1.readFileSync)(configAbsolutePath) .toString() .replace(/["']@nx\/next["']/, `'./.nx-helpers/compiled.js'`) // TODO(v17): Remove this once users have all migrated to new @nx scope and import from '@nx/next' not the deep import paths. .replace('@nx/next/plugins/with-nx', './.nx-helpers/compiled.js') .replace('@nrwl/next/plugins/with-nx', './.nx-helpers/compiled.js')); // Find all relative imports needed by next.config.js and copy them to the dist folder. const moduleFilesToCopy = getRelativeFilesToCopy(configRelativeToProjectRoot, projectRoot); for (const moduleFile of moduleFilesToCopy) { const moduleFileDir = (0, path_1.dirname)((0, path_1.join)(context.root, options.outputPath, moduleFile)); (0, node_fs_1.mkdirSync)(moduleFileDir, { recursive: true }); // We already generate a build version of package.json in the dist folder. if (moduleFile !== 'package.json') { (0, node_fs_1.copyFileSync)((0, path_1.join)(context.root, projectRoot, moduleFile), (0, path_1.join)(context.root, options.outputPath, moduleFile)); } } } function readSource(getFile) { return { file: getFile(), content: (0, node_fs_1.readFileSync)(getFile()).toString(), }; } // Exported for testing function getWithNxContent({ file, content } = readSource(() => (0, path_1.join)(__dirname, '../../../../plugins/with-nx.js'))) { const withNxSource = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true); const getWithNxContextDeclaration = (0, js_1.findNodes)(withNxSource, ts.SyntaxKind.FunctionDeclaration)?.find((node) => node.name?.text === 'getWithNxContext'); if (getWithNxContextDeclaration) { content = (0, devkit_1.applyChangesToString)(content, [ { type: devkit_1.ChangeType.Delete, start: getWithNxContextDeclaration.getStart(withNxSource), length: getWithNxContextDeclaration.getWidth(withNxSource), }, { type: devkit_1.ChangeType.Insert, index: getWithNxContextDeclaration.getStart(withNxSource), text: (0, devkit_1.stripIndents) `function getWithNxContext() { return { workspaceRoot: '${ // For Windows, paths like C:\Users\foo\bar need to be written as C:\\Users\\foo\\bar, // or else when the file is read back, the single "\" will be treated as an escape character. devkit_1.workspaceRoot.replaceAll('\\', '\\\\')}', libsDir: '${(0, devkit_1.workspaceLayout)().libsDir}' } }`, }, ]); } return content; } function findNextConfigPath(dirname, userDefinedConfigPath) { if (userDefinedConfigPath) { const file = userDefinedConfigPath; if ((0, node_fs_1.existsSync)((0, path_1.join)(dirname, file))) return file; throw new Error(`Cannot find the Next.js config file: ${userDefinedConfigPath}. Is the path correct in project.json?`); } const candidates = [ 'next.config.js', 'next.config.cjs', 'next.config.mjs', 'next.config.ts', ]; for (const candidate of candidates) { if ((0, node_fs_1.existsSync)((0, path_1.join)(dirname, candidate))) return candidate; } throw new Error(`Cannot find any of the following files in your project: ${candidates.join(', ')}. Is this a Next.js project?`); } // Exported for testing function getRelativeFilesToCopy(fileName, cwd) { const seen = new Set(); const collected = new Set(); function doCollect(currFile) { // Prevent circular dependencies from causing infinite loop if (seen.has(currFile)) return; seen.add(currFile); const absoluteFilePath = (0, path_1.join)(cwd, currFile); const content = (0, node_fs_1.readFileSync)(absoluteFilePath).toString(); const files = getRelativeImports({ file: currFile, content }); const modules = ensureFileExtensions(files, (0, path_1.dirname)(absoluteFilePath)); const relativeDirPath = (0, path_1.dirname)(currFile); for (const moduleName of modules) { const relativeModulePath = (0, path_1.join)(relativeDirPath, moduleName); collected.add(relativeModulePath); doCollect(relativeModulePath); } } doCollect(fileName); return Array.from(collected); } // Exported for testing function getRelativeImports({ file, content, }) { const source = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true); const callExpressionsOrImportDeclarations = (0, js_1.findNodes)(source, [ ts.SyntaxKind.CallExpression, ts.SyntaxKind.ImportDeclaration, ]); const modulePaths = []; for (const node of callExpressionsOrImportDeclarations) { if (node.kind === ts.SyntaxKind.ImportDeclaration) { modulePaths.push(stripOuterQuotes(node.moduleSpecifier.getText(source))); } else { if (node.expression.getText(source) === 'require') { modulePaths.push(stripOuterQuotes(node.arguments[0].getText(source))); } } } return modulePaths.filter((path) => path.startsWith('.')); } function stripOuterQuotes(str) { return str.match(/^["'](.*)["']/)?.[1] ?? str; } // Exported for testing function ensureFileExtensions(files, absoluteDir) { const extensions = ['.js', '.cjs', '.mjs', '.json', '.ts']; return files.map((file) => { const providedExt = (0, path_1.extname)(file); if (providedExt && extensions.includes(providedExt)) return file; const ext = extensions.find((ext) => (0, node_fs_1.existsSync)((0, path_1.join)(absoluteDir, file + ext))); if (ext) { return file + ext; } else { throw new Error(`Cannot find file "${file}" with any of the following extensions: ${extensions.join(', ')}`); } }); }