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

212 lines (211 loc) • 9.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = addSvgrToNextConfig; const tslib_1 = require("tslib"); const devkit_1 = require("@nx/devkit"); const executor_options_utils_1 = require("@nx/devkit/src/generators/executor-options-utils"); const tsquery_1 = require("@phenomnomnominal/tsquery"); const ts = tslib_1.__importStar(require("typescript")); async function addSvgrToNextConfig(tree) { const projects = new Map(); // Find all Next.js projects using withNx that have svgr explicitly set to true or an options object (0, executor_options_utils_1.forEachExecutorOptions)(tree, '@nx/next:build', (options, project, target) => { const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, project); const nextConfigPath = `${projectConfig.root}/next.config.js`; if (!tree.exists(nextConfigPath)) return; const content = tree.read(nextConfigPath, 'utf-8'); if (!content.includes('withNx')) return; const sourceFile = (0, tsquery_1.ast)(content); let svgrValue; const nextConfigDeclarations = (0, tsquery_1.query)(sourceFile, 'VariableDeclaration:has(Identifier[name=nextConfig]) > ObjectLiteralExpression'); if (nextConfigDeclarations.length > 0) { const objLiteral = nextConfigDeclarations[0]; const nxProp = objLiteral.properties.find((prop) => ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'nx'); if (nxProp && ts.isObjectLiteralExpression(nxProp.initializer)) { const svgrProp = nxProp.initializer.properties.find((prop) => ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'svgr'); if (svgrProp) { // It's an object with options if (ts.isObjectLiteralExpression(svgrProp.initializer)) { svgrValue = {}; for (const prop of svgrProp.initializer.properties) { if (!ts.isPropertyAssignment(prop)) continue; if (!ts.isIdentifier(prop.name)) continue; const key = prop.name.text; if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) { svgrValue[key] = true; } else if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword) { svgrValue[key] = false; } } } // It's a boolean else { svgrValue = svgrProp.initializer.kind === ts.SyntaxKind.TrueKeyword; } } } } // If svgr is not defined, skip this project if (svgrValue === undefined) return; projects.set(nextConfigPath, { svgrOptions: svgrValue, }); }); if (projects.size === 0) return; // Update next.config.js files to add SVGR webpack configuration for (const [nextConfigPath, config] of projects.entries()) { let content = tree.read(nextConfigPath, 'utf-8'); const sourceFile = (0, tsquery_1.ast)(content); const changes = []; const nextConfigDeclarations = (0, tsquery_1.query)(sourceFile, 'VariableDeclaration:has(Identifier[name=nextConfig]) > ObjectLiteralExpression'); if (nextConfigDeclarations.length > 0) { const objLiteral = nextConfigDeclarations[0]; const nxProp = objLiteral.properties.find((prop) => ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'nx'); if (nxProp && ts.isObjectLiteralExpression(nxProp.initializer)) { const svgrProp = nxProp.initializer.properties.find((prop) => ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'svgr'); if (svgrProp) { const nxObj = nxProp.initializer; const hasOnlySvgrProperty = nxObj.properties.length === 1; // Remove svgr property and leave remaining ones if (hasOnlySvgrProperty) { changes.push({ type: devkit_1.ChangeType.Delete, start: nxObj.getStart(), length: nxObj.getEnd() - nxObj.getStart(), }); changes.push({ type: devkit_1.ChangeType.Insert, index: nxObj.getStart(), text: '{}', }); } // If svgr property is the only property, remove the entire nx object else { const propIndex = nxObj.properties.indexOf(svgrProp); const isLastProp = propIndex === nxObj.properties.length - 1; const isFirstProp = propIndex === 0; let removeStart = svgrProp.getFullStart(); let removeEnd = svgrProp.getEnd(); // Handle comma removal if (!isLastProp) { // Remove trailing comma const nextProp = nxObj.properties[propIndex + 1]; removeEnd = nextProp.getFullStart(); } else if (!isFirstProp) { // Remove preceding comma from previous property const prevProp = nxObj.properties[propIndex - 1]; const textBetween = content.substring(prevProp.getEnd(), svgrProp.getFullStart()); const commaIndex = textBetween.indexOf(','); if (commaIndex !== -1) { removeStart = prevProp.getEnd() + commaIndex; } } changes.push({ type: devkit_1.ChangeType.Delete, start: removeStart, length: removeEnd - removeStart, }); } } } } // Only add SVGR webpack config if svgrOptions is true or an object if (config.svgrOptions === true || typeof config.svgrOptions === 'object') { let svgrOptions = ''; if (config.svgrOptions === true) { svgrOptions = `{ svgo: false, titleProp: true, ref: true, }`; } else if (typeof config.svgrOptions === 'object') { const options = Object.entries(config.svgrOptions) .map(([key, value]) => ` ${key}: ${value}`) .join(',\n'); svgrOptions = `{\n${options}\n }`; } const svgrWebpackFunction = `(config) => { const originalWebpack = config.webpack; // @ts-ignore config.webpack = (webpackConfig, ctx) => { // Add SVGR support with webpack 5 asset modules webpackConfig.module.rules.push({ test: /\.svg$/, oneOf: [ { resourceQuery: /url/, type: 'asset/resource', generator: { filename: 'static/media/[name].[hash][ext]', }, }, { issuer: { not: /\.(css|scss|sass)$/ }, resourceQuery: { not: [ /__next_metadata__/, /__next_metadata_route__/, /__next_metadata_image_meta__/, ], }, use: [ { loader: require.resolve('@svgr/webpack'), options: ${svgrOptions}, }, ], }, ], }); return originalWebpack ? originalWebpack(webpackConfig, ctx) : webpackConfig; }; return config; };`; const pluginsArrayDeclarations = (0, tsquery_1.query)(sourceFile, 'VariableDeclaration:has(Identifier[name=plugins]) ArrayLiteralExpression'); if (pluginsArrayDeclarations.length > 0) { const pluginsArray = pluginsArrayDeclarations[0]; const pluginsStatement = pluginsArray.parent.parent; changes.push({ type: devkit_1.ChangeType.Insert, index: pluginsStatement.getEnd(), text: `\n\n// Add SVGR webpack config function\n// @ts-ignore\nconst withSvgr = ${svgrWebpackFunction};`, }); } const composePluginsCalls = (0, tsquery_1.query)(sourceFile, 'CallExpression[expression.name=composePlugins]'); if (composePluginsCalls.length > 0) { const composeCall = composePluginsCalls[0]; const spreadArg = composeCall.arguments.find((arg) => ts.isSpreadElement(arg)); if (spreadArg) { changes.push({ type: devkit_1.ChangeType.Insert, index: spreadArg.getEnd(), text: ', withSvgr', }); } } } content = (0, devkit_1.applyChangesToString)(content, changes); tree.write(nextConfigPath, content); } await (0, devkit_1.formatFiles)(tree); }