@nx/next
Version:
171 lines (170 loc) • 8.42 kB
JavaScript
"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(', ')}`);
}
});
}